導入
printf("%d\n", x) のように1つだけ値を渡しても、printf("%d %d %d\n", a, b, c) のように3つ渡しても、同じ printf がコンパイルできてしまいます。関数の引数の数が呼び出しごとに変わる――これを実現しているのが可変長引数という仕組みです。stdarg.h を使えば、自分の関数でも同じことができます。
説明
可変長引数を受け取る関数は、決まった引数のあとに ... を書きます。中身を読み取るには <stdarg.h> の3つの道具を使います。
va_list… 「今どこまで読んだか」を覚えておく特別な型。va_start(リスト, 最後の名前付き引数)… 読み取りを開始する準備。va_arg(リスト, 型)… 次の引数を指定した型として読み取り、読み取り位置を1つ先に進める。va_end(リスト)… 読み取り終了の後始末。
#include <stdio.h>
#include <stdarg.h>
int sum_all(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main(void) {
printf("合計: %d\n", sum_all(3, 10, 20, 30));
printf("合計: %d\n", sum_all(5, 1, 2, 3, 4, 5));
return 0;
}
sum_all は、関数自身では「いくつ引数が渡されたか」も「その型が何か」も知る手段がありません。そこで最初の引数 count に「これから何個読むか」を渡してもらい、va_start(args, count) で「count の次から可変長引数が始まる」と教えています。va_arg(args, int) を呼ぶたびに、次の引数を int として1つ読み取り、読み取り位置が自動的に進みます。
flowchart LR s["va_start(args, count)<br/>読み取り開始位置を決める"] --> a1["va_arg(args, int) 1回目"] a1 --> a2["va_arg(args, int) 2回目"] a2 --> dots["…count回くり返す"] dots --> e["va_end(args)"]
printf が「何個・どんな型の引数が来るか」を知っているのは、書式指定子(%d や %s)を先頭から数えているからです。同じ考え方で、書式文字列を自分で走査する簡易版 my_printf も作れます。
#include <stdio.h>
#include <stdarg.h>
void my_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
for (const char *p = fmt; *p != '\0'; p++) {
if (*p == '%' && *(p + 1) == 'd') {
printf("%d", va_arg(args, int));
p++;
} else if (*p == '%' && *(p + 1) == 's') {
printf("%s", va_arg(args, const char *));
p++;
} else {
putchar(*p);
}
}
va_end(args);
}
int main(void) {
my_printf("名前: %s, 年齢: %d\n", "Taro", 20);
return 0;
}
この回はプリプロセッサ非対応のJSCPPではなく、本物のCコンパイラで実行されます。標準入力(scanf)は使わず、値はコードの中に直接書いてください。
やってみよう
sum_all に渡す個数や値を変えて呼び出してみましょう。my_printf の呼び出しの引数(名前・年齢)を書き換えて、結果が変わることも確かめてください。
演習
可変長引数を受け取り、渡された整数の中から最大値を返す関数 int max_of(int count, ...) を書いてください。max_of(4, 3, 9, 2, 7) の結果を printf で表示してください(結果は 9)。
ヒント1を見る
va_list args; va_start(args, count); から始め、for で count 回 va_arg(args, int) を読みます。
ヒント2を見る
最初の値を仮の最大値にしておき、if (v > best) best = v; のように更新していきます。