コマンドライン 解答ページ | Programming Place Plus C言語編 第45章

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

問題①

問題① コマンドライン引数から、0個以上の整数を受け取り、その合計を標準出力に出力するプログラムを作成してください。


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

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>

int my_strtol(const char* str, int radix, long int* result);

int main(int argc, char *argv[])
{
    if (argc < 2) {
        fputs("コマンドライン引数が不足しています。\n", stderr);
        exit(EXIT_FAILURE);
    }

    
    long int sum = 0;

    for (int i = 1; i < argc; ++i) {
        long int num;

        if (my_strtol(argv[i], 10, &num) == 0) {
            fprintf(stderr, "%s は有効な整数値ではありません。\n", argv[i]);
            exit(EXIT_FAILURE);
        }
        sum += num;
    }

    printf("合計:%ld\n", sum);
}

/*
    strtol関数をラップしたもの
    引数:
        str:    変換元の文字列。strtol関数の第1引数と同じ。
        radix:  基数。strtol関数の第3引数と同じ。
        result: 変換結果を受け取るポインタ変数。変換に失敗した場合には、何も格納されない。
    戻り値:
        変換が成功したら 0以外、失敗したら 0 が返される。
*/
int my_strtol(const char* str, int radix, long int* result)
{
    assert(str != NULL);
    assert(result != NULL);

    errno = 0;
    char* end;
    long int num = strtol(str, &end, radix);
    if (errno == ERANGE) {
        if (num == LONG_MAX) {
            fputs("変換結果が上限値を超えた。\n, stderr);
        }
        if (num == LONG_MIN) {
            fputs("変換結果が下限値を超えた。\n, stderr);
        }
        return 0;
    }
    else if (str == end) {
        fputs("1文字も変換できなかった。\n", stderr);
        return 0;
    }

    *result = num;
    return 1;
}

コマンドライン引数

30 55 -20

実行結果

65

渡されるコマンドライン引数の個数は不明ですから、勝手に 3個であるとか、5個以下であるとか想定してはいけません。 これに関しては、argc の値を調べるだけの話なので、それほど難しくはないでしょう。 また、argv[0] はプログラムの名前が入る場所なので、見ないようにします。

コマンドライン引数は文字列として渡されるので、整数化しないと合計を計算できません。文字列から整数への変換には、strtol関数を使います。本編で解説したように、strtol関数はエラーチェックが行えるものの、非常に面倒ではあるので、ラップした関数を使っています。

問題②

問題② コマンドライン引数に、

test 15 * -3

のように、「整数」「演算子」「整数」を渡したとき、全体を計算式とみなして計算結果を標準出力へ出力するプログラムを作成してください。(環境によっては、* の部分がうまく解釈されないかもしれません。その場合は、

test 15 '*' -3

のように、’ ’ で囲むことを試してみてください。


#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>

int my_strtol(const char* str, int radix, long int* result);


int main(int argc, char *argv[])
{

    if (argc < 4) {
        fputs("コマンドライン引数が不足しています。\n", stderr);
        exit(EXIT_FAILURE);
    }

    long int left;
    if (my_strtol(argv[1], 10, &left) == 0) {
        fprintf(stderr, "%s は有効な整数値ではありません。\n", argv[1]);
        exit(EXIT_FAILURE);
    }
    long int right;
    if (my_strtol(argv[3], 10, &right) == 0) {
        fprintf(stderr, "%s は有効な整数値ではありません。\n", argv[3]);
        exit(EXIT_FAILURE);
    }

    long int ans = 0;

    switch (argv[2][0]) {
    case '+':
        ans = left + right;
        break;
    case '-':
        ans = left - right;
        break;
    case '*':
        ans = left * right;
        break;
    case '/':
        if (right == 0) {
            fputs("除算において、右側の数値が 0 であってはいけません。\n", stderr);
        }
        ans = left / right;
        break;
    case '%':
        if (right == 0) {
            fputs("除算において、右側の数値が 0 であってはいけません。\n", stderr);
        }
        ans = left % right;
        break;
    default:
        fputs("演算子が無効です。\n", stderr);
        exit(EXIT_FAILURE);
        break;
    }

    printf("ans: %ld\n", ans);
}

/*
    strtol関数をラップしたもの
    引数:
        str:    変換元の文字列。strtol関数の第1引数と同じ。
        radix:  基数。strtol関数の第3引数と同じ。
        result: 変換結果を受け取るポインタ変数。変換に失敗した場合には、何も格納されない。
    戻り値:
        変換が成功したら 0以外、失敗したら 0 が返される。
*/
int my_strtol(const char* str, int radix, long int* result)
{
    assert(str != NULL);
    assert(result != NULL);

    errno = 0;
    char* end;
    long int num = strtol(str, &end, radix);
    if (errno == ERANGE) {
        if (num == LONG_MAX) {
            fputs("変換結果が上限値を超えた。\n, stderr);
        }
        if (num == LONG_MIN) {
            fputs("変換結果が下限値を超えた。\n, stderr);
        }
        return 0;
    }
    else if (str == end) {
        fputs("1文字も変換できなかった。\n", stderr);
        return 0;
    }

    *result = num;
    return 1;
}

コマンドライン引数

15 * -3

実行結果

-45

strtol関数を使えば、数値化するのは簡単なので、結局のところ、それほど難しくはないはずです。

この解答例では、演算子の部分はやや手を抜いています。 2つ目のコマンドライン引数の1文字目だけを参照しているので、“*a” のように余分な文字が付いていても無視されています。 こういう部分もしっかりエラーチェックしてみても良いでしょう。

問題③

問題③ C言語のソースファイルやヘッダファイルを読み込んで、コメント部分を除去した結果を出力するプログラムを作成してください。読み込むファイルの名前は、コマンドライン引数から受け取るようにしてください。


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

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

bool get_next_char(FILE* fp, char* c);
bool skip_to_next_line(FILE* fp);

int main(int argc, char* argv[])
{
    if (argc < 2) {
        fputs("コマンドライン引数が不足しています。\n", stderr);
        exit(EXIT_FAILURE);
    }


    FILE* fp = fopen(argv[1], "r");
    if (fp == NULL) {
        fprintf(stderr, "%s のオープンに失敗しました。\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    int in_comment = 0;
    char c[2];

    for (;;) {
        if (!get_next_char(fp, &c[0])) {
            break;
        }

        if (in_comment) {
            // コメントの終端かどうか
            if (c[0] == '*') {

                // コメントの終端かどうかは、2文字読み取らないと判断できない
                if (!get_next_char(fp, &c[1])) {
                    // ファイルの末尾が '*' の可能性があるので、忘れずに c[0] の出力が必要
                    putchar(c[0]);
                    break;
                }

                if (c[1] == '/') {
                    in_comment = 0;
                }
                else {
                    // コメントの終端でなかったら、読みすぎた1文字を返す
                    ungetc(c[1], fp);

                    putchar(c[0]);
                }
            }
            else {
                putchar(c[0]);
            }
        }
        else {
            // コメントの開始かどうか
            if (c[0] == '/') {

                // コメントの開始かどうかは、2文字読み取らないと判断できない
                if (!get_next_char(fp, &c[1])) {
                    // ファイルの末尾が '/' の可能性があるので、忘れずに c[0] の出力が必要
                    putchar(c[0]);
                    break;
                }

                if (c[1] == '*') {
                    // 「/* ~ */」形式のコメント開始
                    in_comment = 1;
                }
                else if (c[1] == '/') {
                    // 「//」形式のコメント開始

                    // 改行文字が現れるまで読み飛ばす
                    if (!skip_to_next_line(fp)) {
                        break;
                    }
                }
                else{
                    // コメントの始端でなかったら、読みすぎた1文字を返す
                    ungetc(c[1], fp);

                    putchar(c[0]);
                }
            }
            else {
                putchar(c[0]);
            }
        }
    }

    if (fclose(fp) == EOF) {
        fputs("ファイルクローズに失敗しました。\n", stderr);
        exit(EXIT_FAILURE);
    }
}

/*
    次の1文字を受け取る。

    引数
        fp:     対象ファイルのポインタ
        c:      結果を受け取るメモリアドレス

    戻り値
        正常に文字を受け取れたら true、終端に達したら false。
        false が返された場合、引数c が指す先に変化は起こらない。

        エラー発生時は、exit関数によってプログラムが終了する。
*/
bool get_next_char(FILE* fp, char* c)
{
    assert(fp != NULL);
    assert(c != NULL);


    int tmp = fgetc(fp);

    if (tmp == EOF) {
        if (feof(fp)) {
            return false;
        }
        else if (ferror(fp)) {
            fputs("読み込み中にエラーが発生しました。\n", stderr);
            exit(EXIT_FAILURE);
        }
        else {
            // 有効な文字なので、そのまま続行
        }
    }

    *c = (char)tmp;
    return true;
}

/*
    改行文字まで読み飛ばす

    引数
        fp:     対象ファイルのポインタ

    戻り値
        終端に達した場合は false、達しなかった場合は true

        エラー発生時は、exit関数によってプログラムが終了する。
*/
bool skip_to_next_line(FILE* fp)
{
    assert(fp != NULL);

    char c;
    for (;;) {
        if (!get_next_char(fp, &c)) {
            return false;
        }

        if (c == '\n') {
            // 改行文字自体は出力しないといけないので、押し戻す
            ungetc(c, fp);

            return true;
        }
    }
}

実行結果

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

bool get_next_char(FILE* fp, char* c);
bool skip_to_next_line(FILE* fp);

int main(int argc, char* argv[])
{
    if (argc < 2) {
        fputs("コマンドライン引数が不足しています。\n", stderr);
        exit(EXIT_FAILURE);
    }


    FILE* fp = fopen(argv[1], "r");
    if (fp == NULL) {
        fprintf(stderr, "%s のオープンに失敗しました。\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    int in_comment = 0;
    char c[2];

    for (;;) {
        if (!get_next_char(fp, &c[0])) {
            break;
        }

        if (in_comment) {

            if (c[0] == '*') {


                if (!get_next_char(fp, &c[1])) {

                    putchar(c[0]);
                    break;
                }

                if (c[1] == '/') {
                    in_comment = 0;
                }
                else {

                    ungetc(c[1], fp);

                    putchar(c[0]);
                }
            }
            else {
                putchar(c[0]);
            }
        }
        else {

            if (c[0] == '/') {


                if (!get_next_char(fp, &c[1])) {

                    putchar(c[0]);
                    break;
                }

                if (c[1] == '*') {

                    in_comment = 1;
                }
                else if (c[1] == '/') {



                    if (!skip_to_next_line(fp)) {
                            break;
                    }
                }
                else {

                    ungetc(c[1], fp);

                    putchar(c[0]);
                }
            }
            else {
                putchar(c[0]);
            }
        }
    }

    if (fclose(fp) == EOF) {
        fputs("ファイルクローズに失敗しました。\n", stderr);
        exit(EXIT_FAILURE);
    }
}


    次の1文字を受け取る。

    引数
        fp:     対象ファイルのポインタ
        c:      結果を受け取るメモリアドレス

    戻り値
        正常に文字を受け取れたら true、終端に達したら false。
        false が返された場合、引数c が指す先に変化は起こらない。

        エラー発生時は、exit関数によってプログラムが終了する。

bool get_next_char(FILE* fp, char* c)
{
    assert(fp != NULL);
    assert(c != NULL);


    int tmp = fgetc(fp);

    if (tmp == EOF) {
        if (feof(fp)) {
            return false;
        }
        else if (ferror(fp)) {
            fputs("読み込み中にエラーが発生しました。\n", stderr);
            exit(EXIT_FAILURE);
        }
        else {
            // 有効な文字なので、そのまま続行
        }
    }

    *c = (char)tmp;
    return true;
}


    改行文字まで読み飛ばす

    引数
        fp:     対象ファイルのポインタ

    戻り値
        終端に達した場合は false、達しなかった場合は true

        エラー発生時は、exit関数によってプログラムが終了する。

bool skip_to_next_line(FILE* fp)
{
    assert(fp != NULL);

    char c;
    for (;;) {
        if (!get_next_char(fp, &c)) {
            return false;
        }

        if (c == '\n') {

            ungetc(c, fp);

            return true;
        }
    }
}

実行結果は、このプログラム自体を指定して実行したものです。

このサンプルプログラムでは、1文字ずつ読み取りながら、コメントの開始を探しています。コメント部分が開始したら、変数in_comment に真となる値を代入し、その後はコメント終端を探す挙動に変化するように制御しています。

コメントの開始を表す /* にせよ、終端を表す */ にせよ、2文字読み取らないと判断できません。今回は、/* を探しているときには / が、*/ を探しているときには * が現れたときにだけ、もう1文字多く読み込みを行っています。2文字目を読み込んでみた結果、コメントの始端や終端であることが分かったら、前述した、変数in_comment の値を変更することで、挙動の制御を行います。

2文字目を読み込んでみた結果、コメントの始端や終端でなかった場合、余分に読み取った2文字目の方を、ungetc関数を使って押し戻しています。

問題④

問題④ コマンドライン引数から、ファイルパスを2つ指定し、1つ目のファイルの内容を、2つ目のファイルへ追記するプログラムを作成してください。 たとえば、

test in.txt out.txt

としたとき、in.txt の内容を、out.txt の末尾へ追記します。


追記書き込みを行うには、fopen関数の第2引数に “a” を指定します。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    if (argc < 3) {
        fputs("コマンドライン引数が不足しています。\n", stderr);
        exit(EXIT_FAILURE);
    }

    FILE* fp_in = fopen(argv[1], "r");
    if (fp_in == NULL) {
        fprintf(stderr, "%s のオープンに失敗しました。\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    FILE* fp_out = fopen(argv[2], "a");  // 追記モードで開く
    if (fp_out == NULL) {
        fprintf(stderr, "%s のオープンに失敗しました。\n", argv[2]);
        exit(EXIT_FAILURE);
    }

    for (;;) {
        int c = fgetc(fp_in);
        if (c == EOF) {
            if (feof(fp_in)) {
                break;
            }
            else if (ferror(fp_in)) {
                fputs("読み込み中にエラーが発生しました。\n", stderr);
                exit(EXIT_FAILURE);
            }
            else {
                // 有効な文字なので、そのまま続行
            }
        }

        if (fputc(c, fp_out) == EOF) {
            fputs("書き込み中にエラーが発生しました。\n", stderr);
            exit(EXIT_FAILURE);
        }
    }

    if (fclose(fp_in) == EOF) {
        fputs("ファイルクローズに失敗しました。\n", stderr);
        exit(EXIT_FAILURE);
    }
    if (fclose(fp_out) == EOF) {
        fputs("ファイルクローズに失敗しました。\n", stderr);
        exit(EXIT_FAILURE);
    }
}

入力ファイル (in.txt)

すせそ
たちつてと

出力ファイル (out.txt) 実行前

あいうえお
かきくけこ
さし

出力ファイル (out.txt) 実行後

あいうえお
かきくけこ
さしすせそ
たちつてと



参考リンク


更新履歴

≪さらに古い更新履歴を展開する≫



第45章のメインページへ

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

Programming Place Plus のトップページへ



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