ループのネスト

for、while、do ループはネストすることができる。ネストするループは外側とは別のループでもいい。

次のプログラムは三重の for ループを使っている。アルファベットの A から Z までを2回ずつ、それを3回出力する。

#include

int main(void)
{
    int i, j, k;

    for (i = 0; i < 3; i++) {
        for (j = 0; j < 26; j++) {
            for (k = 0; k < 2; k++) {
                printf("%c", 'A' + j);
            }
        }
    }
    printf("\n");

    return 0;
}

実行:

takatoh@nightschool $ ./sample_3_6
AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZAABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZAABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ

doループ

do ループの一般的な形式。

do {
    文;
    ....
} while (条件式);

条件式が真のあいだループするのは while ループと似ているけど、あと判定なので条件式が偽でも1回は必ずループする。最後にセミコロンが必要なので注意。

前のエントリのプログラムを do ループで書くとこうなる。

#include

int main(void)
{
    char ch;

    do {
        ch = getchar();
    } while (ch != 'q');

    printf("q was given.\n");

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_3_5
a
b
x
q
q was given.

whileループ

while ループの一般的な形式。

while (条件式) {
    文;
    ....
}

次のプログラムは q が入力されるまで、ループする。

#include

int main(void)
{
    char ch;

    ch = getchar();

    while (ch != 'q') {
        ch = getchar();
    }

    printf("q was given.\n");

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_3_4
a
b
x
q
q was given.

練習問題3.3

「独習 C」の練習問題 3.3 から。
ループ変数を 1 から 1000 まで、2倍ずつ変更しながら出力する。結果として、等比数列が得られるプログラム。

#include

int main(void)
{
    int i;

    for (i = 1; i <= 1000; i = i * 2) {
        printf("%d ", i);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ ./practice_3_3_3
1 2 4 8 16 32 64 128 256 512

forループふたたび

C の for ループは柔軟にできている。初期設定部でループ変数を初期化しなくてもいいし、インクリメント部でインクリメントしなくてもいい。他にもいろいろな書き方ができる。例を見ながら試してみよう。

条件判定部にループ変数以外を使う例

#include

int main(void)
{
    int i;
    char ch;

    ch = 'a';

    for (i = 0; ch != 'q'; i++) {
        printf("Times: %d\n", i);
        ch = getchar();
    }

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_3_3a
Times: 0
a
Times: 1
Times: 2
b
Times: 3
Times: 4
q

なんか思ったのと違うな……ああ、そうか。文字を取得するのに getchar() を使ってるから入力した a とか b のほかに改行文字を取得してループが回ってるのか。たぶん getche() を使えればこうはならないんだろう。

初期設定部が空の例

for 文の中の式は空でもいい。次の例では、初期値を入力から受け取っているので、for の初期設定部が空になっている。

#include

int main(void)
{
    int i;

    printf("Input positive integer: ");
    scanf("%d", &i);

    for (; i; i--) {
        printf("%d\n", i);
    }

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_3_3b
Input positive integer: 5
5
4
3
2
1

ターゲットが空の例

ターゲット(for ループで実行する文)が空でもいい。次の例では q が入力されるまでループが回り続ける。初期設定部とインクリメント部で入力を受け付けているのにも注目。

#include

int main(void)
{
    int ch;

    for (ch = getchar(); ch != 'q'; ch = getchar()) {
        ;
    }

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_3_3c
a
b
x
q

無限ループ

初期設定部、条件判定部、インクリメント部をすべて空にすると無限ループになる。条件判定部に式がないと、コンパイラはそれを真と仮定するようだ。

for ( ; ; ) {
    ....
}

インクリメントをターゲット内で行う例

ループ変数をインクリメント部以外で変更しても構わない。次の例ではターゲット内で変更しているため、インクリメント部が空になっている。

#include

int main(void)
{
    int i;

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

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_3_3d
0
1
2
3
4
5
6
7
8
9

if文のネスト

if 文はネストすることができる。

if (count > max)
    if (error)
        printf("Error: Try again.\n");

else に続いてさらに if がネストするのを if-else-if の梯子(if-else-if ladder)とか if-lese-if の階段(if-else-if staircase)などと呼ぶらしい。
一般的には次の通り。

if (式)
    文;
else
    if (式)
        文;
    else
        if (式)
            文;
        else
            文;

ただし、これだとインデントがむやみに深くなるので、普通は次のように書く。

if (式)
    文;
else if (式)
    文;
else if (式)
    文;
else
    文;

もっと言えば、たとえ文が1つしかなくてもコードブロックを作ったほうが見やすいと思うな。

次のプログラムは、円、長方形、三角形のいずれかを選択して、その面積を計算する。

#include

int main(void)
{
    char ch;
    float x, y;

    printf("Choice: Circle(C), Rectangle(R), Triangle(T): ");
    ch = getchar();

    if (ch == 'C') {
        printf("Radius? ");
        scanf("%f", &x);
        printf("%f\n", x * x * 3.14);
    } else if (ch == 'R') {
        printf("X? ");
        scanf("%f", &x);
        printf("Y? ");
        scanf("%f", &y);
        printf("%f\n", x * y);
    } else if (ch == 'T') {
        printf("Width? ");
        scanf("%f", &x);
        printf("Height? ");
        scanf("%f", &y);
        printf("%f\n", 0.5 * x * y);
    } else {
        printf("Error.\n");
    }

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_3_2
Choice: Circle(C), Rectangle(R), Triangle(T): C
Radius? 2.5
19.625000
takatoh@nightschool $ ./sample_3_2
Choice: Circle(C), Rectangle(R), Triangle(T): R  
X? 2.0
Y? 3.5
7.000000
takatoh@nightschool $ ./sample_3_2
Choice: Circle(C), Rectangle(R), Triangle(T): T
Width? 4.0
Height? 3.0
6.000000
takatoh@nightschool $ ./sample_3_2
Choice: Circle(C), Rectangle(R), Triangle(T): A
Error.

getchar()とgetche()

getchar()

getchar() はキーボードから1文字読み込む関数。ただし、1文字だけ入力したのでは読み込んでくれなくて、ラインバッファに保存されている状態になっている。Enterキーが押された時点ではじめて1文字読み込む。この時、ラインバッファには文字が残っていて、他の入力要求、例えば scanf などで読み込むことができる。
なんか面倒だけど、歴史的経緯があるそうな。

#include

int main(void)
{
    char ch;

    ch = getchar();
    printf("Typed character: %c.\n", ch);

    return 0;
}

実行結果。書く入力の最後に Enter キーを押している。

takatoh@nightschool $ ./sample_3_1a
x
Typed character: x.
takatoh@nightschool $ ./sample_3_1a
xyz
Typed character: x.

getche()

ラインバッファを利用しない、1文字入力の関数もある。本には「ANCI C で規定された関数でないものの、ほとんどのコンパイラはそれを getche() という名前で呼んでいます。」と書かれている。また、getche() を使うには conio.h ヘッダファイルが必要だとも。
というわけで、次のようなプログラムを書いた。

#include
#include

int main(void)
{
    char ch;

    printf("Input a character: ");
    ch = getche();
    printf("\nASCII code is %d.\n", ch);

    return 0;
}

ところが、これをコンパイルするとエラーになる。

takatoh@nightschool $ gcc sample_3_1b.c -o sample_3_1b
sample_3_1b.c:2:19: fatal error: conio.h: そのようなファイルやディレクトリはありません
 #include <conio.h>
                   ^
compilation terminated.

どうやら gcc は「ほとんどのコンパイラ」に含まれないらしい。ググってみてもやはり gcc には getche() も conio.h もないようだ。
ま、ないものはないので仕方がない。次に進むことにしよう。