プログラムで「もし〜ならA、でなければB」という分岐は、読みやすさと安全性を両立させるために基本を正しく押さえることが大切です。
本稿ではC言語のif文・else文の基礎から、真偽の評価、比較・論理演算子、可読性のための作法、実用的な例、典型的な落とし穴、三項演算子との使い分けまでを丁寧に解説します。
基本構文
C言語で条件分岐を行う最も基本的な構文は次のとおりです。
else if
を挟むことで分岐を増やせます。
A・Bの処理が1行でも、波括弧は常につけるのが安全です。
if (条件式) {
/* Aの処理 */
} else {
/* Bの処理 */
}
/* else ifで分岐を追加可能 */
最小例:正・負・ゼロを判定
次のプログラムは入力された整数が正か負かゼロかを判定して表示します。
#include <stdio.h>
int main(void) {
int x;
/* 値の入力を促す */
printf("整数を入力してください: ");
if (scanf("%d", &x) != 1) {
/* 入力失敗を検出 */
printf("入力エラー\n");
return 1;
}
if (x > 0) { /* 正のとき */
printf("positive\n");
} else if (x < 0) { /* 負のとき */
printf("negative\n");
} else { /* それ以外(ゼロ) */
printf("zero\n");
}
return 0;
}
整数を入力してください: 42
positive
条件式の評価(真・偽)
Cでは「真偽値」の根底が数値で表現されます。
0は偽、0以外は真として評価されます。
ポインタではNULL
が偽、非NULL
が真です。
C99以降はstdbool.h
のbool
/true
/false
を使うと意図が明確になります。
0は偽、0以外は真
#include <stdio.h>
#include <stdbool.h>
int main(void) {
int a = 0;
int b = -3; /* 負でも0でなければ真 */
bool t = true; /* 明示的な真偽型 */
bool f = false;
if (a) { printf("a is true\n"); }
if (b) { printf("b is true\n"); }
if (t) { printf("t is true\n"); }
if (!f) { printf("f is false\n"); }
/* 真偽値の数値表現を出力 */
printf("true=%d, false=%d\n", true, false);
return 0;
}
b is true
t is true
f is false
true=1, false=0
ポインタはNULLが偽、非NULLが真
#include <stdio.h>
int main(void) {
int x = 10;
int *p = &x;
int *q = NULL;
if (p) { printf("p is non-NULL\n"); }
if (!q) { printf("q is NULL\n"); }
return 0;
}
p is non-NULL
q is NULL
比較・論理演算子の要点
条件式では比較演算子と論理演算子を組み合わせます。
代表的なものは次のとおりです。
- 比較:
==
(等しい),!=
(等しくない),<
,<=
,>
,>=
- 論理:
&&
(AND),||
(OR),!
(NOT) &&
と||
は左から短絡評価(ショートサーキット)されます。左側の値だけで結果が確定すると右側は評価されません。
短絡評価の挙動を確認する
次のプログラムは、右側の評価がスキップされる様子を出力で確認します。
#include <stdio.h>
/* 評価されたことが分かるように表示してから値を返す */
int probe(const char *label, int value) {
printf("probe(%s) called, returns %d\n", label, value);
return value;
}
int main(void) {
puts("ANDの例(左が0=偽)");
if (probe("L", 0) && probe("R", 1)) {
puts("AND: true");
} else {
puts("AND: false");
}
puts("\nORの例(左が1=真)");
if (probe("L", 1) || probe("R", 0)) {
puts("OR: true");
} else {
puts("OR: false");
}
return 0;
}
ANDの例(左が0=偽)
probe(L) called, returns 0
AND: false
ORの例(左が1=真)
probe(L) called, returns 1
OR: true
if / else if / else の使い分け
分岐が複数あるときは、条件が「排他的か」「範囲が網羅されるか」を意識し、広い条件を先に書かないように順序を設計します。
if (x > 10) {
/* A: 11以上 */
} else if (x > 0) {
/* B: 1〜10 */
} else {
/* C: 0以下 */
}
順序が悪い例と良い例
例えばx > 0
とx > 10
を誤って逆にすると、x > 0
が先に真になってしまい、後続のx > 10
には決して到達しません。
/* 悪い例:広い条件が先 */
if (x > 0) {
/* ここで1以上すべてが処理され、x > 10に到達しない */
} else if (x > 10) {
/* 到達不能 */
}
/* 良い例:狭い条件から先に判定 */
if (x > 10) {
/* 11以上 */
} else if (x > 0) {
/* 1〜10 */
} else {
/* 0以下 */
}
波括弧と可読性のルール
単文でも波括弧{}
を常に付けると、後から行を追加した際のバグ(「ぶら下がりelse」や意図しない分岐拡張)を防げます。
また、else
が結びつく相手は字下げではなく構文で決まる点に注意します。
ぶら下がりelseの例
/* 誤解を招く書き方:インデントと実際の結び付きが異なる */
if (cond1)
if (cond2)
puts("A");
else
puts("B");
/* 実際にはこう解釈される(elseは直前のif(cond2)に結び付く) */
if (cond1) {
if (cond2) {
puts("A");
} else {
puts("B");
}
}
/* 推奨:常にブレースを使って意図を明示 */
if (cond1) {
if (cond2) {
puts("A");
}
} else {
puts("B");
}
実用例(数値判定・入力チェック)
現場で頻出する分岐の具体例を2つ示します。
偶奇で分岐
#include <stdio.h>
int main(void) {
int n;
if (scanf("%d", &n) != 1) {
puts("input error");
return 1;
}
if (n % 2 == 0) {
printf("even\n");
} else {
printf("odd\n");
}
return 0;
}
入力: 4
even
入力: 5
odd
入力値の範囲チェック
#include <stdio.h>
int main(void) {
int score;
if (scanf("%d", &score) != 1) {
puts("input error");
return 1;
}
if (score < 0 || score > 100) {
printf("invalid\n");
} else {
printf("ok\n");
}
return 0;
}
入力: -5
invalid
入力: 88
ok
よくある落とし穴
日常的に起きやすいバグの種をまとめます。
どれもif-elseの可読性と正しさに直結します。
=(代入)と==(比較)の取り違え
#include <stdio.h>
int main(void) {
int x = 123;
if (x = 0) { /* 代入してしまっている(xは0に) */
puts("zero");
} else {
puts("non-zero"); /* こちらが必ず実行される(条件は常に偽) */
}
return 0;
}
non-zero
正しくは比較演算子==
を使います。
#include <stdio.h>
int main(void) {
int x = 123;
if (x == 0) {
puts("zero");
} else {
puts("non-zero");
}
return 0;
}
non-zero
浮動小数点の直接比較は避ける
丸め誤差のため、a == b
のような直接比較は意図通りに動かないことがあります。
許容誤差(イプシロン)で比較します。
#include <stdio.h>
#include <math.h> /* fabs */
int main(void) {
double a = 0.1 * 3.0;
double b = 0.3;
const double eps = 1e-12;
printf("a=%.17f, b=%.17f\n", a, b);
if (fabs(a - b) < eps) {
puts("ほぼ等しい");
} else {
puts("異なる");
}
return 0;
}
/* 注: 環境によってはリンク時に -lm が必要です */
a=0.30000000000000004, b=0.29999999999999999
ほぼ等しい
& と &&、| と || の混同に注意(ビット演算子と論理演算子は別物)
&
や|
はビット単位の演算子で、短絡評価をしません。
条件式で「両方が真か/どちらかが真か」を判定したいときは&&
/||
を使います。
短絡評価がないと副作用や例外につながることがあります。
#include <stdio.h>
int main(void) {
int x = 0;
/* 短絡あり:左が偽のため右は評価されず安全 */
if ((x != 0) && (10 / x > 1)) {
puts("safe and true");
} else {
puts("safe and false");
}
/* 短絡なし:両辺が評価されるため0除算が起きる危険 */
/* if ((x != 0) & (10 / x > 1)) { ... } ← これは危険 */
return 0;
}
また、ビットフラグの検査には&
を使うのが正しい使い方です。
/* 例:フラグの検査 */
#define FLAG_A 0x01
#define FLAG_B 0x02
if ((flags & FLAG_A) != 0) {
/* FLAG_A が立っている */
}
副作用のある条件式に注意
条件式に副作用(変数の変更、関数呼び出しによる状態変更)があると、短絡評価の有無で実行結果が変わります。
#include <stdio.h>
int call(const char *name) {
puts(name);
return 1;
}
int main(void) {
int i = 0, n = 5, m = 10;
/* i++ が2回評価される。増分回数が意図通りかを確認 */
if (i++ < n && i++ < m) {
/* ここでは i は2増えている可能性がある */
}
printf("i=%d\n", i);
/* 短絡で右が呼ばれないことがある */
if (0 && call("should not print")) {
/* 実行されない */
}
return 0;
}
副作用は最小化し、条件式では評価と更新を分離して書くと安全です。
三項演算子との比較
短い代入や式として値を返したい場面では、三項演算子が有用です。
result = cond ? A : B; /* 短い代入に向く */
使い分けの指針と例
- 表現(値)を返す用途に適します。複雑な処理や副作用がある場合は
if-else
で明確に書きます。 - ネストした三項演算子は読みにくくなりやすいため、可読性を優先します。
#include <stdio.h>
int main(void) {
int x = 7;
/* 三項演算子:値をそのまま代入したい */
const char *kind = (x % 2 == 0) ? "even" : "odd";
printf("%d is %s\n", x, kind);
/* if-else:出力や複数ステップの処理があるときに向く */
if (x % 2 == 0) {
puts("even branch: 追加の処理をここで実行");
} else {
puts("odd branch: ロギングや別処理を実行");
}
return 0;
}
7 is odd
odd branch: ロギングや別処理を実行
比較の観点を表で整理します。
観点 | 三項演算子 ?: | if-else |
---|---|---|
主用途 | 値を返す簡潔な式 | 複数文の処理、分岐の明示 |
可読性 | 短いとき高い、ネストで低下 | 一般に高い |
副作用 | 避けるべき | 許容しやすいが最小化推奨 |
デバッグ | しづらい | しやすい(ブレークポイント設置が容易) |
まとめ
if-elseは条件分岐の基本であり、正しく使うほどプログラムは読みやすく、安全になります。
条件は「狭いものから広いものへ」「網羅性を意識して」並べ、単文でも必ず波括弧を付けることで将来の変更に強いコードになります。
0/非0の真偽、NULL
とbool
、比較・論理演算子の正しい使い分けと短絡評価の意味を理解し、=
と==
、&
と&&
の取り違えなど典型的な落とし穴を避けてください。
値を選ぶだけなら三項演算子で簡潔に、複雑な処理ならif-elseで明確にと使い分けるのが実践的です。