for文 | Programming Place Plus C言語編 第12章

トップページC言語編

このページの概要

以下は目次です。


ループ構造(反復構造)

第11章では、switch文を使って、電卓のようなプログラムを作成しました。実行する処理を切り替えられますが、実行するたびに1つの入力と計算しかできないことが不満といえそうです。色々な式の入力を繰り返したいと思ったら、プログラムを再度実行しなおす必要があります。このページではこの問題の解決に取り組むことにします。

必要なことは、さきほど文章の中にちらっと登場した「繰り返し」です。プログラミングではよく、ループ (loop) という言葉で表現しますが、同じコードを何度も繰り返し実行する構造のことをいいます。

やりたいことを書き表すとこうなります。

#include <stdio.h>

int main(void)
{
    #define ADDITION        '+'
    #define SUBTRACTION     '-'
    #define MULTIPLICATION  '*'
    #define DIVISION        '/'

    // ここから ------>
    puts("Please enter the formula.");
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value1;
    int value2;
    char op;
    sscanf(s, "%d %c %d", &value1, &op, &value2);

    switch (op) {
    case ADDITION:
        printf("%d\n", value1 + value2);
        break;
    case SUBTRACTION:
        printf("%d\n", value1 - value2);
        break;
    case MULTIPLICATION:
        printf("%d\n", value1 * value2);
        break;
    case DIVISION:
        printf("%d\n", value1 / value2);
        break;
    default:
        puts("The formula is incorrect.");
        break;
    }
    // <----- ここまでのコードを、気が済むまで繰り返したい
}

コメントで書いたとおりですが、電卓プログラムの中核となっている部分、つまり「入力を受け取って、計算して、結果を出力する部分」を何度も繰り返したいということです。

ループさせるものは「まったく同じ内容の処理」ではなく「同じコード」であることに注意してください。変数を使っているコードをループさせるのなら、その変数に入っている値は毎回異なるかもしれませんし、入力を受け取るコードをループさせるのなら、実際に入力されてくるデータは毎回異なるでしょう。

繰り返す回数が固定的であれば、コードを必要な回数分だけコピーアンドペーストすることで、無理やり実現させることも不可能ではありませんが、プログラムが肥大化して管理しづらくなりますし、プログラマーの労力も大きいです(1万回繰り返したいといわれたらどうしますか?)。また、今回の電卓プログラムのように、繰り返す回数が固定的でなく、“実行した人がやめたくなるまで” のようなケースでは、どれだけコピーアンドペーストしていいのか分かりませんから、この方法では実現できません。

for文

C言語でループを実現する方法はいくつかあります。このページではそのうちの1つである for文 (for statement) を取り上げます。

for文の文法は次のようになっています。

for (; 条件式;)
    繰り返したい文

これまでのページでも何度か説明していますが、「式」と「文」はきちんと区別しましょう。

式は、評価されて(計算などをして)値になるものです。たとえば、n + 5 は式であり、評価(変数 n の値に 5 を加算する)して、値(計算結果)になります。値になるので、それをさらに代入することが可能です(たとえば、answer = n + 5)。代入も式なので、さらに連結して、value = answer = n + 5 のようにつないでいけます。

文は、いくつかの式を含んだものですが、最終的な値がありません。一連の式の処理がすべて落ち着くまでが1つの文といえます。たとえば、さきほどの最後の式の末尾に ; を付けて、value = answer = n + 5; とすると、これ全体で、一連の式が完結した1つの文になります。

n + 5 は式、n + 5; は文ですから、構文を書き表すときには、「文;」という表現は正しくなくて、「式;」か「文」とすべきです。さきほどの for文の文法に戻ると、「繰り返したい文」には ; を付けていません。「節」と「条件式」には ; を付けています。「式」には ; がありませんが、これは ) で終わりが分かるからです。

本当は最後の「式」にも ; が付いたほうが一貫性があるという考え方もありますが、少なくともC言語はそうなっていません。; を入れるとコンパイルエラーになってしまいます。

一定の回数だけループする

具体的なコードで確認します。

for文を使う目的として多いのは、一定の回数だけループしたいというものです。たとえば、3回だけループしたいときには、次のように書きます。

for (int i = 0; i < 3; i = i + 1)
    puts("Hello");

まず、「節」のところに int i = 0 と書きました。プログラムの実行中、for文のところにやってくると、まずここが実行されます。「節」では必ずしも変数を宣言して初期化する記述を書かなければならないわけではなく、ほかの目的の式も書けます。ともかく、最初に1回だけ実行してほしいことを書きます。

変数名の i は、for文でよく使われる伝統的な名前です。「変数名は意味が分かりやすいようにせよ」と何度か書きましたが、i という名前は for文で使う場合にかぎっては、十分に分かりやすい名前です。

「節」の実行を終えた直後、まず「条件式」がチェックされます。「条件式」のところには i < 3 と書いています。これは、コードの繰り返し実行を続ける条件を表していて、i < 3 は「変数 i の値が 3 より小さいあいだ」を意味しています。条件式の記述のしかたについては、きちんと取り上げると長くなるので、このページでは深入りしませんが、for文の典型的な使い方をしている限りは、i < 繰り返す回数 であると思って問題ありません。3回繰り返したいから i < 3、5回なら i < 5 ということです。

「条件式」をチェックした結果、繰り返す条件が満たされていたら、「繰り返したい文」へ進みます。繰り返す条件が満たされていなかったら、for文の実行は終わりです。

「繰り返したい文」には、puts("Hello"); と書いてあります。したがって、「“Hello” という文字列を出力して改行する」というコードを繰り返したいということです。3回繰り返すのですから、

Hello
Hello
Hello

という結果を期待しているわけです。

実際の動きとしては、「繰り返したい文」が一気に3回実行されるわけではなく、まず1回だけ実行されます。1回の実行が終わったら、「式」のところに進みます。「式」には i = i + 1 と書かれているので、変数 i の値が 1つ増えます。変数 i の初期値は 0 ですから、「繰り返したい文」を1回実行したあとに 1 になります。結局のところ変数 i は、現在までに「繰り返したい文」が何回実行済みであるかをあらわしていることになります。

なお、「節」で宣言した変数の値を、「繰り返したい文」の中で使っても構いません。ただし、その変数の値を書き換える行為は避けたほうがいいです。ループさせる回数がおかしくなってしまいます。

「式」の実行を終えたら、再び「条件式」をチェックします。繰り返す条件が満たされていたら「繰り返したい文」へ進み、満たされていなかったら、for文の実行は終わりです。あとは同じ流れで、「繰り返したい文」>「式」>「条件式」のところを、「条件式」の内容を満たさなくなるまで、ぐるぐると回り続けます。


for文で「ある回数だけ繰り返す」という使い方は、古くからよくある典型的なものです。そのため、昔からよくある書き方があって、それは次のようなものです。

for (int i = 0; i < 3; ++i) {
    puts("Hello");
}

最初に示したコードとの違いは、以下の2箇所の書き方だけです。動作に違いはありません。

  1. 「式」の書き方(i = i + 1++i になった)
  2. 「繰り返したい文」が {} で囲まれている

これらの違いについては、この後で解説します。

結局、一定回数のループを必要とするときに書き換えなければならないのは、「条件式」のところと、「繰り返したい文」のところだけです。

for (int i = 0; i < /*繰り返す回数*/; ++i) {
    //
    // 繰り返したい処理
    //
}

インクリメント

「式」の部分が、++i という記述になりました。+ を2つ並べた見慣れないこの表記は、インクリメント演算子 (increment operator) と呼ばれるもので、構文は次のようになります。

++変数名
変数名++

【上級】正確には「変数名」ではなく、変更可能な左辺値とされています。1

インクリメント演算子の効果は、対象の変数の値を +1 するというものです。インクリメント演算子を使うかどうかにかかわらず、+1 する行為をインクリメント (increment) と呼びます。

インクリメント演算子には、変数名の手前に置く前置前置インクリメント)(pre-increment) と、変数名のうしろに置く後置後置インクリメント)(post-increment) という2つの使い方があります。

前置でも後置でも、対象の変数の値をインクリメントするという効果は同じですが、違いがあるケースも存在します。これは後で取り上げます

for文の「式」のところで使う限り、以下の記述はすべて同じ意味になります。

  1. i = i + 1
  2. i += 1
  3. ++i
  4. i++

インクリメント演算子を使うと、代入の = が登場しないことが少し特徴的で注意すべきところです。= がなくても、変数の値が変更されるということです。i = ++i; のようにはしませんし、してはいけません。

【上級】i = ++i; のような書き方をしていけないのは、1つの式の中で、同じ変数の値を2回以上変更する行為は、未定義の動作だからです2 。このコードでは、変数 i の値が代入とインクリメントで2回変更されようとしています。i = i + 1; なら、代入のときに1回変更するだけなので問題ありません。

記述が簡潔なので、基本的にはインクリメント演算子を使えばいいです。この使い方では ++ii++ に違いはないのでどちらを使っても構いません。

ブロック文

もう1つの違いである {} の存在についてです。

最初に示した for文の構文は、次のようになっていました。

for (; 条件式;)
    繰り返したい文

このように、構文ルールからいって、{} はありません。しかし、世の中のC言語のコードのほとんどは、{} を付けています。

「繰り返したい文」とだけ書かれていると、いくらでも文を置いていいようにみえるかもしれませんが、実際にはそうではなくて、置ける文は1つだけです。そのため、次のコードは問題ありませんが、

for (int i = 0; i < 3; ++i)
    puts("Hello, World.");

次のコードは、おそらく想定した意味のコードになっていません。

for (int i = 0; i < 3; ++i)
    puts("Hello, ");
    puts("World.");

これだと、puts("World."); は for文の「繰り返したい文」から外れていることになります。つまり、3回 “Hello,” を出力したあと、1回だけ “World.” を出力します。

「繰り返したい文」に1文しか置けないのでは不便です。そこで、{} を使います。

{} は、ブロック文 (block statement) と呼ばれる文の一種で、0個以上の文をひとまとめにして、1つの文とみなすものです。これを使えば、複数の文を書いても、まとめて1つの文ですから許されるというわけです。

よく、「{} は文が1つなら省略できる」と説明されますが、これは話が逆です。構文ルール上、文は1つしか置けず、2つ以上置きたいなら {} を付けなければならないのです。

書きたい文が1つだけだとしても、ブロック文を使って構いません。 さきほどの正しくないコード例のような間違いを防ぐ意味でも、むしろブロック文にした方がいいともいえます。

電卓プログラムでの利用例

ブロック文を使えば、for文の「繰り返したい文」のところに複数の文が書けるので、このページの目的であった、電卓プログラムの改良ができます。

とりあえず、5回繰り返すようにしてみます。

#include <stdio.h>

int main(void)
{
    #define ADDITION        '+'
    #define SUBTRACTION     '-'
    #define MULTIPLICATION  '*'
    #define DIVISION        '/'

    for (int i = 0; i < 5; ++i) {
        puts("Please enter the formula.");
        char s[40];
        fgets(s, sizeof(s), stdin);
        int value1;
        int value2;
        char op;
        sscanf(s, "%d %c %d", &value1, &op, &value2);

        switch (op) {
        case ADDITION:
            printf("%d\n", value1 + value2);
            break;
        case SUBTRACTION:
            printf("%d\n", value1 - value2);
            break;
        case MULTIPLICATION:
            printf("%d\n", value1 * value2);
            break;
        case DIVISION:
            printf("%d\n", value1 / value2);
            break;
        default:
            puts("The formula is incorrect.");
            break;
        }
    }
}

実行結果:

Please enter the formula.
1 + 2   <-- 入力した式
3
Please enter the formula.
10 - 1   <-- 入力した式
9
Please enter the formula.
3 * 5   <-- 入力した式
15
Please enter the formula.
5 / 2   <-- 入力した式
2
Please enter the formula.
-1 + 3   <-- 入力した式
2

うまくいっているようです。

さて、こうなると新たな不満点が出てきます。なにより、なぜ5回なのかという点が気になります。普通、「電卓の利用者の用事が済むまで」繰り返すべきです。5回では足りないかもしれないし、多すぎるかもしれません。

しかし、終わりたいときに終了できるようにするには、まだ少し知識が足りません。この問題にはこの先のページで取り組んでいくことにして、ここからは for文に関するほかの話題を続けます。

ループ内の変数

さきほどの電卓プログラムでは、for文の内側に変数の宣言があります(value1、op、value2)。

for文の内側にに変数の宣言を書いた場合、処理がループして、その宣言位置にやってくるたびに、新しい変数として初期化しなおされます。前のループのときの値を引き継ぐようなことはありません。

もし、前のループで計算した値を、次のループで使いたい、といった場合には、for文の外側で宣言する必要があります。

たとえば、整数の入力を5回行わせて、最後にその合計値を出力させるプログラムを作りたいとします。この場合、これまでの入力の合計値を覚えておく変数が必要ですが、その変数は for文の外側で宣言しなければなりません。

#include <stdio.h>

// 整数の入力を受け取る
// 戻り値: 標準入力から整数の入力を受け取り、その値を返す。
int get_input_integer(void)
{
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value;
    sscanf(s, "%d", &value);
    return value;
}

int main(void)
{
    int total = 0;

    for (int i = 0; i < 5; ++i) {
        puts("Please enter the integer.");
        int value = get_input_integer();
        total += value;
    }

    printf("total = %d\n", total);
}

実行結果:

Please enter the integer.
6   <-- 入力した整数
Please enter the integer.
3   <-- 入力した整数
Please enter the integer.
5   <-- 入力した整数
Please enter the integer.
1   <-- 入力した整数
Please enter the integer.
8   <-- 入力した整数
total = 23

前置インクリメントと後置インクリメントの違い

for文の話から外れますが、前置インクリメントと後置インクリメントで違いがうまれる場面を確認しておきます。違いがうまれるのは、結果をほかの変数に入れる場合です。

#include <stdio.h>

int main(void)
{
    int value;
    int result;

    value = 10;
    result = ++value;
    printf("%d, %d\n", value, result);

    value = 10;
    result = value++;
    printf("%d, %d\n", value, result);
}

実行結果:

11, 11
11, 10

変数 value の値が 10 のときに、前置・後置それぞれのインクリメントを行い、その結果を変数 result に代入しています。

いずれの場合でも、変数 value の値は 11 になっていますが、変数 result の方は、前置インクリメントでは 11、後置インクリメントでは 10 となっています。このような違いが生まれるのは、前置のインクリメント演算子と後置のインクリメント演算子では、次のようにルールの違いがあるからです。

前置の場合、オペランドが +1 され、式の評価結果はインクリメント “後” の値になります。3

後置の場合、オペランドが +1 され、式の評価結果はインクリメント “前” の値になります。4

ここでの「式」とは ++valuevalue++ のことであり、オペランドは value のことです。

そのため、result = ++value では、value の値が +1 され、インクリメント演算子の式の評価結果はインクリメント後の値です。評価結果を result に代入するので、元の value が 10 だったのなら、value は 11 ですし、result も 11 になります。

一方、result = value++ では、value の値が +1 され、インクリメント演算子の式の評価結果はインクリメント前の値です(元の値が 10 なら 10 のまま)。評価結果を result に代入するので、value は 11 ですが、result は 10 です。

【上級】インクリメントと代入をおこなう式について、次のような説明がなされることがありますが、これは間違っています。
- 前置では、先にインクリメントして、そのあと代入される
- 後置では、まず代入して、その後でインクリメントされる
しかし、演算子の優先順位からいって、インクリメントよりも代入のほうが後です。そのため、y = ++xy = (x = x + 1) と等価であり、y = x++y = (t = x, x = x + 1, t) と等価であるといえます。

デクリメント

インクリメントが登場したついでに、デクリメント (decrement) にも触れておきます。デクリメントは、値を -1 する操作のことです。

デクリメントを簡潔に記述するために、デクリメント演算子 (decrement operator) があります。記号は -- です。

デクリメント演算子の文法や、挙動はインクリメント演算子とまったく同じです。+1 だったのが -1 になるだけです。

--変数名
変数名--

【上級】正確には「変数名」ではなく、変更可能な左辺値とされています。1

実行できるプログラムを挙げておきます。

#include <stdio.h>

int main(void)
{
    int value;
    int result;

    value = 10;
    result = --value;
    printf("%d, %d\n", value, result);

    value = 10;
    result = value--;
    printf("%d, %d\n", value, result);
}

実行結果:

9, 9
9, 10

実引数の評価順

複数の実引数を渡す場合、それぞれの実引数が評価される順番は保証がないことに注意してください。

たとえば、次のプログラムの実行結果は処理系によって異なる可能性があります。

#include <stdio.h>

void print_values(int v1, int v2, int v3);

int main(void)
{
    int a = 10;
    print_values(a, ++a, a + 2);
}

void print_values(int v1, int v2, int v3)
{
    printf("%d, %d, %d\n", v1, v2, v3);
}

print_values(a, ++a, a + 2) には3つの実引数があります。左から右に処理されると思っていると、10, 11, 13 の順番で指定しているように思えますが実際にはどれから評価されるかは決まっていません。++aaa + 2 の順番で評価されて、11, 11, 13 が渡されることになるかもしれません。

このため、1つの関数呼出しにおいて、実引数の式がどのような順番で評価されても問題ないようにしなければなりません。インクリメントやデクリメントはこの問題に抵触する代表的な例です。

このサンプルプログラムの場合、次のようにインクリメントをあとで行うなどすれば、結果が保証できます。

#include <stdio.h>

void print_values(int v1, int v2, int v3);

int main(void)
{
    int a = 10;
    print_values(a, a + 1, a + 2);
    ++a;
}

void print_values(int v1, int v2, int v3)
{
    printf("%d, %d, %d\n", v1, v2, v3);
}

実行結果:

10, 11, 12


練習問題

問題① 次の3つの文のうち、実行したあとの変数 x の値がほかと違うのはどれですか?

問題② 次のプログラムの実行結果はどうなりますか?

#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 0; ++i) {
        puts("Hello");
    }
}

問題③ 次のプログラムの実行結果はどうなりますか?

#include <stdio.h>

int main(void)
{
    int value = 0;

    for (int i = 0; i < 5; ++i) {
        value += i;
    }
    printf("%d\n", value);
}

問題④ for文を使って、2 から順に 2 のべき乗を出力するプログラムを作成してください(21、22、23、24・・・つまり 2、4、8、16・・・と出力します)。10個出力したところで終了してください。

問題⑤ 整数の入力を繰り返し受け取り、そのつど、現時点までの合計と平均を出力するプログラムを作成してください。全部で5回入力されたら終了とします。

問題⑥ 九九の表を出力するプログラムを作成してください。つまり、次のような出力結果が得られるようにしてください。

1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81


解答ページはこちら

参考リンク


更新履歴



前の章へ (第11章 switch文)

次の章へ (第13章 if文と条件演算子)

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

Programming Place Plus のトップページへ



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