可変個引数 解答ページ | Programming Place Plus C言語編 第52章

トップページC言語編第52章

問題① 🔗

問題① 可変個引数で渡した int型整数の合計値を返す関数を作成してください。可変でない1個目の引数が、可変個部分の引数の個数を表すとします。たとえば、

total = sum(5, 10, -4, 7, -2, 9);

このように呼び出すと、変数total に 10 + (-4) + 7 + (-2) + 9 の結果である 20 が格納されるものとします。


可変個引数を表す「…」の前には、最低1つの void型でない仮引数が必要です。今回の場合、先頭に可変部分の引数の個数を表す仮引数を置いています。

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

int sum(int num, ...);

int main(void)
{
    printf("%d\n", sum(5, 10, -4, 7, -2, 9));
}

/*
    int型の合計値を返す。
    引数:
        num:    渡す値の個数。
    戻り値:
        ... の部分に指定した int型の値の合計を返す。
*/
int sum(int num, ...)
{
    va_list args;
    va_start(args, num);

    int total = 0;
    for (int i = 0; i < num; ++i) {
        total += va_arg(args, int);
    }

    va_end(args);

    return total;
}

実行結果:

20

可変個引数の練習向けのシンプルな関数ですが、実用性はありません。こういう目的の関数を作るにしても、素直に配列(のメモリアドレス)を渡すような関数を作りましょう。可変個引数は、間違った型の引数をうっかり渡せてしまうため、本質的に危険が伴います。またこの例では、第1引数で指定する値を間違えた場合も、想定外の結果を生んでしまいます。

sum(6, 10, -4, 7, -2, 9);    // 第1引数が間違っている
sum(5, 10, -4, 7, -2.5, 9);  // double型の値が混ざっている

問題② 🔗

問題② %d、%f、%c、%s の各変換指定子にだけ対応した、簡易的な printf関数を自作してください。“%3d” などの複雑な仕様は無視して構いません。また、実際に標準出力へ書き出す部分は、本物の printf関数を呼び出して構いませんが、vprintf関数は使わないでください。


こんな関数を自作する必要性はまったくなく、普通に printf関数を使うべきですが、可変個引数の使い方の練習や、本物の printf関数が内部でどう動いているかを考えてみるのは有益だと思います。

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

void my_printf(const char* format, ...);

int main(void)
{
    my_printf("Hello, World\n");
    my_printf("int: %d\n", 100);
    my_printf("float: %f\n", 3.5f);
    my_printf("char: %c\n", '!');
    my_printf("char*: %s\n", "test");
    my_printf("%%\n");
}

/*
    簡易printf関数

    %d、%f、%c、%s だけに対応。
    %3d のような、拡張的な指定には対応していない。
    また、%% による %文字そのものの出力は可能。
*/
void my_printf(const char* format, ...)
{
    va_list args;
    va_start(args, format);

    const char* p = format;
    while (*p != '\0') {

        if (*p == '%') {
            // % の次に続く文字に応じて、処理を分岐させる

            ++p;
            assert(*p != '\0');  // % が末尾にあるのはあり得ない

            switch (*p) {
            case 'd':
                printf("%d", va_arg(args, int));
                break;

            case 'f':
                printf("%f", va_arg(args, float));
                break;

            case 'c':
                printf("%c", va_arg(args, char));
                break;

            case 's':
                printf("%s", va_arg(args, const char*));
                break;

            case '%':
                printf("%%");
                break;

            default:
                assert(!"% に続く文字が不正");
                break;
            }
        }
        else{
            // %以外は普通に出力
            printf("%c", *p);
        }

        // 次の文字へ
        ++p;
    }

    va_end(args);
}

実行結果:

Hello, World
int: 100
double: 3.500000
char: !
char*: test
%

可変個引数の取り扱いは、本編での解説どおりです。あとは、1文字ずつ順番に処理していくだけですが、%文字が現れたときには、その直後の文字によって分岐させる必要があります。

% の次の文字が不正な場合(たとえば %ld のような指定には対応していないので、% の次が l なのは不正となります)や、文字列の末尾が % だった場合を、正しく取り扱える必要があります。この解答例では、assertマクロで停止させています。

問題③ 🔗

問題③ 配列へ要素をまとめて格納する関数を作成してください。たとえば、

assign(array, 5, 0, 1, 2, 3, 4);

このように呼び出すと、int型で要素数が 5 の配列array に、0, 1, 2, 3, 4 という値を順番に格納するものとします。


たとえば、次のようになります。

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

#define SIZE_OF_ARRAY(array)    (sizeof(array)/sizeof(array[0]))

void assign(int* array, size_t size, ...);
void print_array(const int* array, size_t size);

int main(void)
{
    int array[5];

    assign(array, SIZE_OF_ARRAY(array), 3, 5, -2, 0, 4);
    print_array(array, SIZE_OF_ARRAY(array));

    assign(array, SIZE_OF_ARRAY(array), -7, -1, 4, 2, 9);
    print_array(array, SIZE_OF_ARRAY(array));

    assign(array, SIZE_OF_ARRAY(array), 0, 1, 2, 3, 4, 5);
    print_array(array, SIZE_OF_ARRAY(array));
}

/*
    int型配列へ要素をまとめて格納する
    引数:
        array:      格納先配列のメモリアドレス
        size:       array の要素数
        ...:        array に格納する要素の並び

    size の値よりも可変個引数の方が少ない場合の動作は保証しない。
    可変個引数の方が多い場合は、size の分だけ格納する。
*/
void assign(int* array, size_t size, ...)
{
    va_list args;
    va_start(args, size);

    for (size_t i = 0; i < size; ++i) {
        array[i] = va_arg(args, int);
    }

    va_end(args);
}

/*
    配列の中身を標準出力へ出力する。
    引数:
        array:  対象配列のメモリアドレス。
        size:   対象配列の要素数。
*/
void print_array(const int* array, size_t size)
{
    for (size_t i = 0; i < size; ++i) {
        printf("%d ", array[i]);
    }
    printf("\n");
}

実行結果:

3 5 -2 0 4
-7 -1 4 2 9
0 1 2 3 4

print_array関数は、コードを短くすませるために用意しただけです。

assign関数は、多少なりとも実用性のある関数のようですが、エラーチェックがほとんど働かないことに不満があります。これは、可変個引数を使うときにはいつも付きまとう問題です。それでも、配列の要素数を指定させることによって、配列の範囲外に書き込もうとするエラーはある程度は防がれます(その指定を誤らなければ)。


参考リンク 🔗


更新履歴 🔗

≪さらに古い更新履歴≫

 全面的に文章を見直し、修正を行った。

 C言語編全体で表記を統一するため、「フォーマット指定」を「変換指定」に改めた。

 SIZE_OF_ARRAYマクロの定義を修正。

 新規作成。



第52章のメインページへ

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る