C++ 可変個引数 書いて覚えるための初心者自己中記事
printf や sprintf などは引数を大量に入れても問題なく動く。
今までの関数の作り方だと無理。
どうやっているのか。という話。
まず、仮引数のところで ... と書けば可変個引数になる。
可変個引数はそのまんまの意味。
int Sum(int first, ...) { int sum = 0; va_list args; va_start(args, first);//#include <cstdarg> for (int n = first; n != 0; n = va_arg(args, int)) { sum += n; } va_end(args); return sum; } int main() { cout << Sum(1, 2, 3, 4, 5, 0) << endl; system("pause"); }
//output
15
知らない部分がたくさんだ~
まず最初の ... これはさっき言ったやつだ。
int Sum(int first, ...) {
次にこれら
va_list args; va_start(args, first);
for文の中で
n = va_arg(args, int)
で終わりに
va_end(args);
まず、
va_list 型のオブジェクトを作っている。
va_list 型ってなに?
va ってのは可変個引数(variable argument )の va
それの list
可変個の引数のリスト va_list 型
多分そういうことだからこのva_list 型のオブジェクトには引数がたっぷり入りそうな予感。
で、次がマクロ va_start ( , ) ;
第一引数にva_list 型のオブジェクト
第二引数には、Sum 関数の第一引数。 ... の直前の引数。
これは、えっと?第二引数に入っているものをもとにva_list 型のオブジェクトを初期化しているそうです。
??
で、初期化されたそのva_list型オブジェクト args から値(引数)を取り出すのが va_arg( , ); マクロ。
このマクロも第一引数はva_list型オブジェクト args
第二引数で指定した型の値が取り出せるそうです。
一個ずつね。
このマクロを一回呼び出すたびに一つずつ。順番に取り出せる。
最後にva_end ( ); マクロで終了させる。
ん~~、初期化の部分が。
ひとつ前の引数を指定して初期化ってのは、その次以降の引数から始めるよっていう初期化なのか?
そんで、この流れで汎整数昇格(integral promotion)
この現象は式中にint型より下の型
short
char
bool
が登場したときに
int もしくは unsigned int
に暗黙キャストされる現象。
ただし、式中といっても計算がなければ暗黙キャストはおこらない。
たぶんこれって、計算するために最低限int にしないとっていう事で行われるんだろうな。
で、可変個引数でも同じで
short
char
bool
はint 型にキャストされたのちに渡される。
浮動小数点でも、double より下の float はdoubleにキャストされる。
なんでこんな話なのかというと、
n = va_arg(args, int)
ここの第二引数で指定している型
これをbool とかにしてもダメだよって事。
int にキャストされただけだから取り出してから元の型にキャストしなおす。
int Sum(int first, ...) {
for (int n = first; n != 0; n = va_arg(args, int)) {
1つ疑問が残っている。
first ってどうなってるの?
説明がないぞ?
確認しました。
Sum(3, 2, 3, 4, 5, 0)
こんな引数で関数を呼び出して first をcout すると
3
と出ます。(本来の足し算はしてません)
Sum(5, 2, 3, 4, 5, 0)
これだと
5
が出ます。(本来の足し算はしてません)
そして本来一つずつ足されていって合計を出していたところを
ただ一つずつcout させてみると
Sum(5, 2, 3, 4, 5, 0) //output
5
5
2
3
4
5
こうなります。
va_start(args, first);
ここでの初期化というのは第一引数のアドレスとかそんなので初期化しているのでしょうか?
ただ、そうなると
for (int n = first; n != 0; n = va_arg(args, int))
for 文でint n の初期値を決めているfirst は何なのでしょう。
あ、いやまてよ、普段for 文ではn を増やしていているけど今回は違うんだ。
第一引数first 今回は5が入っている。
それを合計値をためていく変数sum に渡して、そのあとに次の引数を取り出しているのか?
あれ?でもそれじゃあ第一引数で初期化したva_list のargs はva_argマクロで取り出すときに初めに取り出されるのが第二引数からってこと?
int Sum(int first, ...) { int sum = 0; va_list args; va_start(args, first); int n; n = va_arg(args, int); cout << n << endl; n = va_arg(args, int); cout << n << endl; n = va_arg(args, int); cout << n << endl; n = va_arg(args, int); cout << n << endl; n = va_arg(args, int); cout << n << endl; va_end(args); return 0; }
Sum(5, 2, 3, 4, 5, 0)
//output
2
3
4
5
0
0
やっぱりそうみたいですね。
しかし、この初期化を第一引数以外でやるとおかしくなりました。
ためしにint 型変数 n = 1;
とかで初期化しても結果がおかしくなりました。
第一引数で初期化しなければダメなんでしょうか。
そして第一引数だけはそのまま使用する形なのでしょうか。
謎が余計に深まったのかもしれない。
汎整数昇格されないもの
引数にポインタを使った場合キャストされません。
void型ポインタとint型ポインタでアドレス表現が違う環境などの場合があって、voidポインタを仮引数にしていてintポインタを実引数にしたりすると、キャストされないので危険。
アップキャストとかも同じことになる。
引数を渡す前にキャストしてあげなければならない。
ただし、なぜか
( const ) void*
( const ) char *
相互は問題ないと規定されているそうです。
可変個引数を使った関数内の処理でprintf関数や sprintf 関数を使いたい場合に
vprintf関数
vsprintf関数
があるらしい。
va_list型のオブジェクトと第一引数をそのまま入れられる関数。
ちょっとまだ使い道がわからない。
スタック
引数などを積み重ねていくメモリ領域
データをどんどん積み重ねていき、不要になったら上から順に捨てていくらしい。
なんでも、可変個引数の関数では変数の末尾から順にスタックに積み重ねられていき最後に関数を呼び出した位置(戻ってくるときのための位置)のアドレスを積んで、それから関数の中に入っていくらしい。
そしてva_startマクロは一番上の戻るためのアドレスの次からが引数だと分かるのでそこからをva_listにセットする。va_list はポインタみたいな感じらしい。
・・・。
第一引数無視されてたじゃん・・。
どういうことよ。
マイクロソフトのページ見たらこんな感じのとこが書いてあった
void va_start(
va_list arg_ptr,
prev_param
);
prev_param
最初の省略可能な引数に先行するパラメーター?
最初の省略可能な引数(省略したくない)
先行する(最初の引数に先行するって?)
パラメーター(パラメーターってなに?)
一行で疑問が3つ。
なんか。うん。もうこういうもんなんだな。
飲み込めてきた気がする。
今回の引数の渡し方が、俺の頭が変になった理由だな。
本当に必要な引数を3個くらい用意してからそのあとに可変個引数、とかの場合の関数もあるわけだからこれで良いのかも。
よし、ここまで