57章 可変引数

http://www.geocities.jp/ky_webid/c/057.html

引数の数を可変にできるということでしょうか。

実は既にそういった可変引数を扱う関数を使ったことがあります。

そうprintfですね。つまりあのような仕様の関数を定義できるということになります。

これは楽しそうですね。

ここでのポイントは

  • stdarg.h
  • va_list型
  • va_startマクロ
  • va_argマクロ
  • va_endマクロ


しかし全然よくわかりません。

ちょっと他のサイトでも調べてみました。

C言語 可変引数 va_start va_arg va_end

なるほどなるほど。...の手前に置く変数の方によって扱い方が変わるってことか。

なんとなくわかってきました。

なのでさっそく練習問題にいきましょう。

問題1

固定の第1引数で指定した文字列と同じ文字列が、可変引数リストの引数の中にあるか調べて、あれば真の値を、なければ偽の値を返す関数を作って下さい。

なるほど。実装してみましょう。

int match (char *str,int num, ...){
    int flag = 0,i;
    va_list list;
    va_start(list,num);
    
    for(i=0;i<num;++i){
        if ( strcmp(str,va_arg(list, char *)) == 0 ) {
            flag = 1;
            break;
        }
    }
    
    va_end(list);
    
    return flag;
}

int main () {
    
    printf("%d\n",match("aa",3,"bb","ee","dd"));
    printf("%d\n",match("aa",3,"bb","aa","dd"));
    
    return 0;
}
$ main
0
1

できました。ばっちりですね。

答え合わせ

同じですね。大正解です。

問題2

%d、%c、%sのフォーマット指定子だけを処理できる独自のprintf関数を作って下さい。ただし、実際の出力処理は、本来のprintf関数を呼び出して構いません。

文字列指定の方法ですね。やってみます。

#include <stdio.h>
#include <stdarg.h>

void myprintf(const char *format, ...) {
    va_list list;
    va_start(list,format);
    
    while(1){
        if ( *format == '%' ) {
            switch(*++format) {
                case 'd':
                    printf("%d",va_arg(list, int));
                    break;
                case 'c':
                    printf("%c",va_arg(list, char));
                    break;
                case 's':
                    printf("%s",va_arg(list, char *));
                    break;
                case '%':
                    printf("%%");
                    break;
            }
        }
        else {
            fputc(*format,stdout);
        }
        if ( *format++ == '\0' ) break;
    }
    
    va_end(list);
}

int main () {
    myprintf("test %d_%c_%s_%%",1,'A',"BBB");
    return 0;
}

なんとかできました。すっごいややこしいです。

もうちょっとキレイにかけそうな気はしますがとりあえずコレで完成ということで。

答え合わせ

大体はあってましたがいくつか気になる点が。

まずまたwhileでやってしまったこと。こういう処理はforでやった方がいいので本当の本当に次から気をつけよう。

そしてswitchのdefaultの時にそのまま出力の処理が抜けてますね。失敗。

問題3

可変引数リストで指定された複数の整数の、平均値を返す関数を作って下さい。戻り値はfloat型で小数表示にします。

平均値ですね。

float average (int argc, ...) {
    float total = 0;
    int i;
    va_list list;
    va_start(list,argc);
    
    for(i=0;i<argc;++i) {
        total += va_arg(list,int);
    }
    
    va_end(list);
    
    return total / (float)argc;
}

int main () {
    printf("%f\n",average(5,10,20,30,40,50));
    printf("%f\n",average(2,43,64));
    return 0;
}
$ main
30.000000
53.500000

できました。難しいところはないですね。

答え合わせ

おっと、ちょっと危なかったですね。

va_argの第二引数はintで指定してますが、total変数をfloatにしてるので微妙かもしれません。

解答例のように足す間はintでやって返す寸前でfloatにキャストした方が効率の面から見ても良さそうですね。気をつけます。