Cでは0=偽、非0=真。シンプルだが罠も多い。C99以降のbool型と合わせて正しい書き方を身につける。
int done = 0; // 0 = 偽 while (!done) { // ... if (condition) done = 1; // 1 = 真 }
#include <stdbool.h> // bool, true, false が使える bool done = false; while (!done) { if (condition) done = true; }
bool は実は _Bool 型のマクロ、true/false は単なる 1/0 のマクロ。_Bool は0か1しか保持しない特殊な型で、「非0を代入すると1になる」特徴がある。_Bool b = 42; // _Bool は 0 と 1 しか保持しないので 1 になる printf("%d\n", b); // → 1 int i = 42; // 普通のintは42のまま printf("%d\n", i); // → 42
if、while、for、?: など)では次のルールが使われます。if (0) { // 実行されない } if (-1) { // 実行される(非0だから真) } if (42) { // 実行される } if (0.0) { // 実行されない } char *p = NULL; if (p) { // 実行されない (NULLは0) }
==、!=、<、>、<=、>= の結果は必ず 0 か 1 の int です。true/false ではなく int であることに注意。int a = 5, b = 3; int r = (a > b); // r は 1 (int) printf("%d\n", r); // → 1 int s = (a < b); printf("%d\n", s); // → 0 // カウンタに足すテクニック int count = 0; for (int i = 0; i < n; i++) { count += (a[i] > 0); // 正の要素の個数を数える }
char *p = malloc(100); if (p) { // NULLでない → 確保成功 free(p); } // 次の2つは同じ意味(好みの問題) if (p) { /* ... */ } if (p != NULL) { /* ... */ }
if (p)、明示派は if (p != NULL)。ほぼ宗教論争だが、可読性重視のコードベースでは NULL 比較を推奨することが多い。double x = 0.1 + 0.2; if (x == 0.3) { // falseになる! // 実際の x ≈ 0.30000000000000004 }
#include <math.h> double x = 0.1 + 0.2; if (fabs(x - 0.3) < 1e-9) { // 許容誤差内で一致 }
if (x == 0.0) で問題ない(0は正確に表現できる)。ただし計算結果として0を期待するなら通常イプシロン比較が安全。if (x == true) が危険な理由int x = 2; // 2 は「真」だが 1 ではない if (x == true) { // NG: true は 1。2 == 1 は false printf("入らない\n"); } if (x) { // OK: 非0なので真として入る printf("入る\n"); }
if (x) / if (!x)。どうしても正規化したいなら後述の !!x イディオムを使う。= と == の取り違えif (x = 5) { // これは「xに5を代入、結果(5)を真と判定」 // 常に真になってしまう }
if (x == 5) { // xが5と等しいかの比較 }
-Wall を付けると if (x = 5) で警告が出る。定数を左に書くテクニック (if (5 == x)) で typo を検出しやすくする派もいる(Yoda conditions)。! の優先順位if (!x && y) // (!x) && y つまり「x が偽 かつ y が真」 if (!(x && y)) // x と y のどちらかが偽 // 意味が違う! 括弧で意図を明示
if (a & b) // ビットAND: a と b の両方のビットが立っている位置がある if (a && b) // 論理AND: a も b も真 // 例: a = 4 (100), b = 3 (011) // a & b → 0 (共通ビットなし) → 偽 // a && b → 真 (両方とも非0)
!!x で 0/1 に正規化! を2回適用すると、任意の値を 0 か 1 に揃えることができます。int x = 42; int normalized = !!x; // 42 → !42=0 → !0=1 → 1 int y = 0; int n2 = !!y; // 0 → !0=1 → !1=0 → 0
bool ready = !!(flags & FLAG_READY);bool b = (x != 0); の方が読みやすい派もいる。_Bool への代入なら自動で 0/1 になるので _Bool b = x; でも同じ。好みとチームのスタイルに合わせる。&& と || は左から右へ評価し、結果が確定した時点で右側を評価しない(短絡評価、short-circuit)。if (p != NULL && p->value > 0) { // p が NULL なら p->value を評価しない(NULL参照回避) }
if (x == 0 || y / x > 10) { // x が 0 なら y/x を評価しない(0除算回避) }
x == 0 || y / x > 10 と y / x > 10 || x == 0 は意味が違う。後者は 0除算を起こしうる。ガード条件は必ず左側に書く。// 関数呼び出しをガード条件付きで実行 if (mode_debug && log_debug(msg)) { // mode_debug が偽なら log_debug は呼ばれない } // デフォルト値の代入(一部のコードベースで使う) int x = get_value() || 10; // ただしCでは結果は 0か1、10にはならない!
|| と違い、Cの || は値そのものを返さない(結果は常に0か1)。JSのようなデフォルト値代入には使えない。printf("%d %d %d %d\n", !0, !1, !!5, !!-3);if (x == true) の罠int x = 2; のとき、if (x == true) と if (x) の分岐結果が異なるコードを書き、違いを printf で確認せよ。0.1 + 0.2 == 0.3 が false になることを確認し、イプシロン比較で true になるよう書き直せ。s を受け取り、「NULLでなく、かつ先頭が 'A' なら 1 を返す」関数を書け。NULL参照でクラッシュしないよう && の順序に注意。