Last Modified :Wednesday, 16-Feb-2005 23:22:13 JST
トップページへ

乱丁, 落丁, 間違い等のご指摘は kurihara.toru [at]kochi-tech.ac.jpまでお願いいたします.
(初版:22/Apr/2004)
(第2版:28/Apr/2004)参考文献を追加
(第3板:16/Feb/2005)SaveCSV2を追加

可変引数リストを用いたプログラミング

はじめに

可変引数リストとは, 引数の個数があらかじめ決まっていない場合における 名前なし引数のリストのことである.

例えば, 実験時のデータの保存をするときに, あれも保存したい、これも保存したいと思うと そのたびに関数の種類を増やすのはあまりにも馬鹿げている. cからc++になって, 同じ関数名でも引数の型によって呼び出される実体が 変わったため多少は楽になったが, 関数をたくさん用意するのは馬鹿げているだろう.

そこで,可変引数リストなるものを使うことにする. 例えば, printfなどは引数の数が決まっていないため, これを用いてプログラムされている.

ヘッダ

#include <stdarg.h> というのが必要なヘッダである.

では, 具体的にfloat型の引数をいくつか受け取り, ファイルにcsv形式で書き出すプログラムを見てみよう.
引数arg_numは引数として受け取るfloat型配列の個数, sizeは配列のサイズである. #include <stdarg.h> void SaveCSV(FILE *fp,int arg_num,int size,...) { int i,k; float **pfval; va_list ap; //可変長リストの宣言 pfval = new float* [arg_num]; va_start(ap,size); //可変長リストの初期化,2つめの変数は最後の名前あり引数 for(k=0;k&lt;arg_num;k++){ pfval[k]=(float*)va_arg(ap,double*);//可変長リストから値を読み出す } for(i=0;i&lt;size;i++){ for(k=0;k&lt;arg_num;k++){ fprintf(fp,"%f,",*(pfval[k]+i)); } fprintf(fp,"\n"); } va_end(ap);//可変長リストの終了 delete [] pfval; } 引数の"..."というのが, 数が決まっていない引数をあらわす部分である. 可変引数リストは,最低1個の名前付引き数と"..."からなり, まず,va_list型として可変長リストapを宣言する.

可変個の引数リストにアクセスを開始するにはva_start()を使用する. 必ずこれを最初に行う必要があるのだ.

void va_start(va_list ap , last);

ここで2個目の引数lastは, 名前のついている固定引数の最後の変数名をいれよう. この時点で, 可変引数リストapには, 次の引数のポインタが格納されている.

次に, 可変引数リストから値を得るにはva_argを用いる.

TYPE va_arg(va_list ap , TYPE);

typeは, 引数リストから受け取る値の型を表している. この型に応じたバイト数だけ左辺の代入される. この例では, float型へのポインタを格納したいのだが, va_argはdouble型へのポインタしか書けないために, キャストを行っている.(g++ではfloat*も通るようです.)

最後に,引数リストを使い終わったら, va_endをしなくてはならない.

void va_end(va_list ap);

例2

上のSaveCSVでは, 引数の配列はすべてfloat型の縛りがありました. 次の例は, 任意の型(下の例ではint, float, double)でできるようにしました. ただし, 美しくありません. どなたかより良い方法がありましたら, 教えてください.
第2引数に書式を指定することで実現しています. たとえば, "dfg"など. ただし, printfのように%つけません. また, printfではfloatもdoubleも%fでしたので, ここでは, f:float, g:doubleということにしています. void SaveCSV2(FILE *fp,char *fmt, int size,...) { int i, k, num; void **pval; //引数リストの指すポインタへのポインタ va_list ap; //可変長リストの宣言 num=strlen(fmt); pval = new void* [num]; va_start(ap, size); //可変長リストの初期化 for(i=0;i&lt;num;i++) pval[i]=va_arg(ap,int*); //ポインタなんだから一緒さ for(i=0;i&lt;size;i++){ for(k=0;k&lt;num;k++){ switch(fmt[k]){ //fmt[k]により, int, float, doubleを振り分ける case 'd': //int型 fprintf(fp, "%d,", *(((int*)pval[k])++));//型により進めるアドレスが違うので正しい型にキャストする break; case 'f': //float型 fprintf(fp, "%f,",*(((float*)pval[k])++)); break; case 'g': //double型 fprintf(fp, "%f,",*(((double*)pval[k])++)); break; } } fprintf(fp,"\n"); } va_end(ap); delete [] pval; }

参考文献


http://black.sakura.ne.jp/~third/programming/c/c62.html
http://www.linux.or.jp/JM/html/LDP_man-pages/man3/stdarg.3.html
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/vclib/html/_crt_va_arg.2c_.va_end.2c_.va_start.asp