プログラムに分岐が必要な理由
ここまでのプログラムはすべて、まっすぐ上から下へ走ってきました。1 行目、2 行目、3 行目。しかし実際のプログラムは判断を下します。パスワードは正しいか? 数値は負か? ファイルは開けたか? if 文は、C が質問を投げかけ、答えがイエスのときだけコードを実行する方法です。
条件 temperature > 30 は一度だけ判定されます。成り立つのでメッセージが表示されます。最後の行は波かっこの外にあるので、何があっても表示されます。31 を 18 に変えてもう一度実行してみてください - 最初のメッセージは消え、2 つ目は残ります。
構文: 括弧、波かっこ、そしてセミコロンなし
C の if の形は決まっています:
if (condition) {
// condition がゼロでないときに実行される
}
早いうちに覚えておく価値のある規則が 3 つ。条件を囲む括弧は必須です。then キーワードはありません。そして閉じ括弧の後にセミコロンは付けません - はぐれたセミコロンはそこで文を終わらせてしまいます:
// バグ: if は何もせず、printf は常に実行される
if (x > 10);
{
printf("大きい\n");
}
これはきれいにコンパイルが通ります。; は空文で、それが if の本体になり、その下の波かっこは常に実行される独立したブロックになります。gcc -Wall でコンパイルすればこれを捕まえられます。
波かっこ自体は 1 つの文なら省略できますが、残しておきましょう。波かっこのない if に後から 2 行目が加わるのは、バグの定番の発生源です。C ではインデントで文をまとめることはできないからです。
else と else if
else はもう一方の経路を与えます:
結果が 3 つ以上あるなら else if で連ねます。C は各条件を順に調べ、最初に成り立ったところで止まります:
ここでは順序がきわめて重要です。連鎖は最初に一致したところで止まるので、2 つ目の判定は 90 未満の点数しか見ません - だからこそ score >= 80 に && score < 90 を足す必要がないのです。最も厳しい条件から最も緩い条件へと書きましょう。さもないとすべての値が最初の分岐に落ちてしまいます。
末尾の else は省略できます。連鎖がすべての場合を覆うつもりなら必ず入れましょう。それがないということは「どれにも一致しなければ何もしない」という意味で、それ自体は問題ありませんが、意図的であるべきです。
真偽: 0 は偽、それ以外はすべて真
C の中核言語には、条件のための独立した真偽型がありません。if は単に、その式がゼロでないかどうかを尋ねます。
最後の 2 行はこの規則のもう半分を示しています。比較演算子はごく普通の式であり、真なら 1、偽なら 0 を生み出します。だから if (5 > 2) が動くのです - 実際には if (1) なのですから。
これが -3 が C で真になる理由でもあります。規則はゼロでないことであって、正であることではありません。数値が正かどうかを知りたいなら if (n) ではなく if (n > 0) と書きましょう。
C99 は <stdbool.h> を追加し、本物の bool 型と true・false を持ち込みました。新しいコードではこちらのほうが読みやすくなります。内部的にはやはり 1 と 0 なので、上の規則は何も変わりません - C言語の真偽値を参照してください。
条件の中で使う比較演算子と論理演算子:
== 等しい && 論理 AND(両辺が真)
!= 等しくない || 論理 OR (どちらかが真)
< より小さい ! 論理 NOT
> より大きい
<= 以下
>= 以上
&& と || は短絡評価します。a && b は a が偽なら b を評価しませんし、a || b は a が真なら b を評価しません。これは最適化ではなく保証なので、if (n != 0 && total / n > 5) と書いても安全です - n がゼロのとき除算は実行されえません。
= と == のバグ
これは C の条件式における最もよくある間違いです:
// バグ: x に 5 を代入し、それから 5(ゼロでない)を判定するので、これは常に実行される
if (x = 5) {
printf("x は 5 です\n");
}
C は式の中での代入を許すので、この行は合法です。5 を x に格納し、値 5 を生み出し、5 はゼロではありません - 分岐は毎回必ず実行され、x は静かに上書きされています。
防御策が 2 つ。第一に、必ず警告を有効にしてコンパイルすること:
gcc -Wall -Wextra program.c -o program
GCC も clang も「真理値として使われる代入は括弧で囲むことを推奨します」と指摘します。第二に、定数を変数の前に書くプログラマもいます - if (5 == x) - こうすれば打ち間違えた if (5 = x) が静かなバグではなく確実なコンパイルエラーになります。このスタイルは好みの問題ですが、警告を有効にすることは好みの問題ではありません。
ネスト
if は別の if を含むことができます。ネストは、先行する条件が成り立って初めて意味を持つ条件を表現します:
2 段なら読めます。4 段は読めません。ネストが深くなってきたら、平らにする方法を探しましょう - たいていは && で条件をまとめるか、失敗する場合を先に処理して早めに return するかです:
// より平ら: 例外を先に片付けてから本題に入る
if (age < 18) { printf("成人のみです。\n"); return 0; }
if (!hasTicket) { printf("チケットをお買い求めください。\n"); return 0; }
printf("ようこそ。\n");
ぶら下がり else
波かっこのない if が別の if の中にネストされているとき、else はどちらの if に属するのでしょうか。C の規則: else は、まだ対応づいていない最も近い if に結びつく - コードがどうインデントされていようと関係ありません。
// インデントに反して、else は内側の if に属する
if (a > 0)
if (b > 0)
printf("両方とも正\n");
else
printf("a は正ではない\n"); // 誤り - a > 0 かつ b <= 0 のときに実行される
インデントは else が外側の if と対になっていると言っています。コンパイラは内側のものと対にします。波かっこを付ければあいまいさは完全に消えます。これこそが、常に波かっこを使うべき本当の理由です:
if (a > 0) {
if (b > 0) {
printf("両方とも正\n");
}
} else {
printf("a は正ではない\n");
}
三項演算子
分岐が値を選ぶためだけに存在するなら、条件演算子 ? : がそれを 1 つの式で言い表します:
cond ? x : y は「cond なら x、そうでなければ y」と読みます。2 つの分岐のうちちょうど 1 つだけが評価され、全体として値を生み出します - だからこそ、文では置けない場所で代入したり引数として渡したりできるのです。
単純な値の選択にとどめましょう。ネストした三項演算子(a ? b : c ? d : e)は合法ですが、置き換える先の else if の連鎖よりほぼ確実に読みにくくなります。
よくある質問
C言語で if else 文はどう書きますか?
条件を括弧に入れ、コードを波かっこに入れます: if (score >= 60) { printf("合格\n"); } else { printf("不合格\n"); }。else ブロックは条件が偽のときだけ実行されます。C に then キーワードはありません。
C言語の if 文では何が真とみなされますか?
ゼロでない値すべてです。C の中核となる構文には専用の真偽型がないので、if (n) は n が 0 以外のときに実行され、if (!n) は n がちょうど 0 のときだけ実行されます。== や < などの比較演算子は、真なら 1、偽なら 0 を生み出します。
C言語の = と == の違いは何ですか?
== は比較し、= は代入します。if (x = 5) と書くと 5 を x に代入してから 5 を判定するので、ゼロでない値となり常に分岐が実行されます。エラーなくコンパイルが通るため、C の条件式における初心者の最も多いバグです - たいていのコンパイラは -Wall で警告してくれます。
C言語の三項演算子はどう動きますか?
condition ? valueIfTrue : valueIfFalse は 2 つの値のどちらかを生み出す式なので、代入や printf の引数の中に置けます: int max = a > b ? a : b;。値を選ぶときに使い、文を実行したいときは普通の if を使いましょう。