条件分岐とループ


if

プログラムは、基本的に上から下へ順に実行されるが、それでは決まった 動作をするプログラムしか作れない。ある条件が成立するかしないかによって 動作が変るようなプログラムを書くには、if文を用いる。

次のプログラムを見て欲しい。

/* Solve Quadratic Equation */

#include <stdio.h>
#include <math.h>

int main(void) {
    double a, b, c, d;

    printf("Solve Quadratic Equation ax^2+bx+c=0\n");

    printf("a=");
    scanf("%lf", &a);
    printf("b=");
    scanf("%lf", &b);
    printf("c=");
    scanf("%lf", &c);

    d = b * b - 4 * a * c;

    if (d > 0) {
        printf("Solution1=");
        printf("%f\n", (-b+sqrt(d))/(2*a));
        printf("Solution2=");
        printf("%f\n", (-b-sqrt(d))/(2*a));
    } else {
        if (d == 0) {
            printf("Solution=");
            printf("%f\n", -b/(2*a));
        } else {
            printf("No Solution.\n");
        }
    }

    return 0;
}
quadeq.c

これは、2次方程式 ax2+bx+c=0の係数をキーボードから 入力し、解を計算するプログラムである。

if文は、

if (条件式) 文1

または、

if (条件式) 文1
else 文2

という形をしている。 前者の場合、条件式が成立すれば「文1」を実行し、成立しなければ何もしない。 後者の場合、条件式が成立すれば「文1」を実行し、成立しなければ「文2」を 実行する。

「文」とは「int a;」などの宣言文や「z = x + y;」などの 式文を指すが、

{
    文
    文
    文
    ....
}

のように「文を複数並べて{}で括ったもの」もまた「文」である(「複文」という)。 従って、if文の中の文に複文を使えば、

if (条件式) {
    文
    文
    ...
}

または、

if (条件式) {
    文
    文
    ...
} else {
    文
    文
    ...
}

のような形になる。普通if文の中に書く実行部分は 複数の文からなることが多いので、むしろこちらがifの形だと 覚えてしまった方が良い。確かに文が1つのときは

if (a == 0) x = 1;
else x = 2;

のように書くことは出来るが、構造が分かりにくくなるのでお奨めしない。 たとえ文が一つでも、

if (a == 0) {
    x = 1;
} else {
    x = 2;
}

と書いた方がよい。

上の2次方程式の例題では、elseの中に更にif文が 入っている。このようにif文は入れ子にすることが出来る。

条件式

C言語では、括弧内の条件式を計算し、それが0であれば偽、0以外で あれば真と見倣す。条件式を記述するための演算子は、 真ならば1、偽ならば0の値を取る。 まず、条件式を書くための基本となる、比較演算子の一覧を示す。

a == b abが等しければ1
a != b abが等しくなければ1
a > b abより大きければ1
a < b abより小さければ1
a >= b abより大きいまたは等しければ1
a <= b abより小さいまたは等しければ1
(もちろんそうでなければ0)

等号が「=」でなく「==」であることに注意。 ここを間違えることは多い。

次に、これらを組み合わせるための、論理演算子をいくつか示す。

a || b abのうち少なくとも片方が真(0でない)なら1
a && b abのうち両方が真(0でない)なら1
! a aが真(0でない)なら0、偽(0)なら1

これを使うと、

if ((1 <= a) && (a <= 9)) {
    文
}
(aが1以上かつ9以下なら文を実行)

のような複雑な条件を書くことが出来る。

while

次の例は、自然数を入力し、その数以上の2の巾乗であるような数を計算し 表示するプログラムである。

#include <stdio.h>

int main(void) {
    int n, a;

    scanf("%d", &n);

    a = 1;
    while (a < n) {
        a *= 2;
    }

    printf("%d\n", a);

    return 0;
}
power.c

while文は、文法的には

while (条件式) 文

だが、ifと同様

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

であると覚えてしまってよい。条件式が成立するか調べ、成立していれば 文を実行、これを繰り返す。条件式が成立しなければ終了し次へ行く。 条件式が成立している限りは繰り返すため、条件式がいつかは成立しなく なるようにプログラムが書かれていなければ、無限ループに陥る危険がある。 無限ループになったら、Ctrl-C (Ctrlキーを押しながらcを押す) で終了できる。

また、「break;」によって強制的にループを脱出し 次へいくことが出来る。 次の例は、自然数を入力するとその平方根を表示する、という 作業を繰り返し、もし負の数が入力されたら終了するという プログラムである。

#include <stdio.h>
#include <math.h>

int main(void) {
    double x;

    while (1) {
        scanf("%lf", &x);
        if (x < 0) break;
        printf("%f\n", sqrt(x));
    }

    return 0;
}
sqrt.c

ここで、「while (1)」は常に条件が成立、すなわち無限ループを 表す。脱出はbreakによって行われる。

このように、ループを書くのにとりあえず「while (1)」で 無限ループにしておき、ifbreakで好きなときに 脱出するという書き方は「私は」書き易いと思う。
なお、do-while文というものもある。こちらは、 文法的には

do 文 while (条件式);

だが、ifと同様

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

であると覚えてしまってよい。普通のwhileとの違いは、 文を実行した後に条件式を調べることである。 無限ループとbreakで書くなら、普通のwhile文は、

while (1) {
    if (! 条件式) break;
    文
    文
    ...
}

であるのに対して、do-while文は

while (1) {
    文
    文
    ...
    if (! 条件式) break;
}

であり、脱出の位置が違う。これを見ると、普通のwhile文は 中身が一度も実行されないことが有り得るのに対して、do-while文は 一度は必ず実行されることが分かる。

for

while文を使って、1から10まで表示するプログラムを 書くと、

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

のようになるであろう。このように、

パターンは非常によく出てくるので、forという別の書き方が 用意されている。上のプログラムは、for文を使うと

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

のように書け、変数を1から10まで増やすということが明確になっている。

for (i=10; i>=1; i--) {
    printf("%d\n", i);
}

のように書くと、10から1まで値を減らしながら表示する。また、

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

のように書くと0,2,4,6,8,10と表示する。

for文は、文法的には

for (式1; 式2; 式3) 文

という形であり、これは

式1;
while (式2) {
    文
    式3;
}

と等価である。

for文は、数値を増やしながらという用途だけでなく、 ループの回る回数を指定するという意味での使い方も多い。例えば、

for (i=0; i<10; i++) {
    printf("Hello World\n");
}

で「Hello World」を10回表示する。

continue

continueは、breakと同様にforwhiledo-whileの中で用いて、 ループ中の残りの部分の実行をやめて末尾までスキップする 命令である。次のプログラムを見て欲しい。

#include <stdio.h>
#include <math.h>

int main(void) {
    double x;

    while (1) {
        scanf("%lf", &x);
        if (x < 0) continue;
        printf("%f\n", sqrt(x));
    }

    return 0;
}
sqrt2.c

これは、入力された数に対しその平方根を計算し表示するものである。 但し入力が負の数なら何もしない。 このプログラムは永久に終了しないため終わらすにはCtrl-Cを入力する。 xが負ならばその次の行は飛ばされ、次のループの実行へと 移っている。

ループ中のcontinueは、例えば

for (i=0; i<10; i++) {
    文1
    文2
    if (条件) continue;
    文3
    文4
}

ならば、

for (i=0; i<10; i++) {
    文1
    文2
    if (! 条件) {
        文3
        文4
    }
}

と同じなので、breakと違って必須の機能というわけではない。

if文のネストに関する注意

次のプログラムは、点数を入力し、

90~100A+
80~89A
70~79B
60~69C
~59F

に従って評価を出力するプログラムである。

#include <stdio.h>

int main(void) {
    int x;

    scanf("%d", &x);

    if (x >= 90) {
        printf("A+");
    } else {
        if (x >= 80) {
            printf("A");
        } else {
            if (x >= 70) {
                printf("B");
            } else {
                if (x >= 60) {
                    printf("C");
                } else {
                    printf("F");
                }
            }
        }
    }
    printf("\n");

    return 0;
}
grade.c

このプログラムは、90点以上ならA+、そうでなくて80点以上ならA、 というような素直なプログラムであるが、極端にif文の ネストが深くなって見辛くなってしまっている。 確かにプログラムの構造を明らかにするのにちゃんと字下げを 行うことは重要であるが、このような連鎖したifの 場合は、特別に次のように書くことが多い。

#include <stdio.h>

int main(void) {
    int x;

    scanf("%d", &x);

    if (x >= 90) {
        printf("A+");
    } else if (x >= 80) {
        printf("A");
    } else if (x >= 70) {
        printf("B");
    } else if (x >= 60) {
        printf("C");
    } else {
        printf("F");
    }
    printf("\n");

    return 0;
}
grade2.c

これはelseに本来あるべき{}を省略していて普通ならば やらない書き方だが、この場合に限っては、

if --- else if --- else if --- else if --- else 文
とでも言うべき文があると思って理解するのがよい。

switch-case

switch-case文は、

switch (式) {
    case 値1:
        文
        break;
    case 値2:
        文
        break;
    case 値3:
        文
        break;
    default:
        文
}

のような形で、式の値を計算し、その対応する値のラベルへジャンプするという 動作をする。default:は、対応する値が無かった場合に飛ぶ 場所で、無くてもよい。default:が無い場合、対応する値が 無ければswitch文は何もしない。

式の値は、int型でなくてはならない。

文の後にあるbreak;は、switchから脱出するためのもので、 これがないとそのまま次の値に対する実行文の部分まで突入してしまう。

前の成績を出力するプログラムをswitchで書くと、

#include <stdio.h>

int main(void) {
    int x;

    scanf("%d", &x);

    switch (x / 10) {
        case 10:
        case 9:
            printf("A+");
            break;
        case 8:
            printf("A");
            break;
        case 7:
            printf("B");
            break;
        case 6:
            printf("C");
            break;
        default:
            printf("F");
    }
    printf("\n");

    return 0;
}
grade3.c

のように書ける。「case 10:」の部分では、あえてbreak; を書かずに次へ走り抜けることを利用している。

goto

goto文は、ラベルをつけて、そこに強制的にジャンプする 命令である。 例えば、
while (1) {
    文
    文
    ...
}

は、
label1:
    文
    文
    ...
    goto label1;

と書くことも出来る。

便利なものであるが、乱用するとプログラムの流れが分かりづらくなるので、 よほどのことが無い限り使ってはならないとされている。


条件分岐とループ