Menu

C の演算子:算術・論理・優先順位

知っておく価値のある C の演算子をひと通り(算術・代入・比較・論理・インクリメント)、さらに整数除算の罠、負の数での剰余の振る舞い、そして信頼できる優先順位表。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

演算子は C の動詞です。値を取り、新しい値を生み出します。全部で 40 個ほどありますが、大半のコードは十数個で足ります。そのうち 2 つは、独立した節を割く価値があるほど鋭い罠を抱えています。

算術演算子

(書式文字列中の %% はパーセント記号そのものを表示します。% ひとつでは書式指定子が始まってしまいます。)

2 項の算術演算子は + - * / % の 5 つ、それに符号反転の単項 - と、事実上何もしない + があります。

整数除算の罠

整数同士の a / b整数除算を行い、余りを捨てます。丸めは行われません。結果はゼロ方向に切り捨てられます。

最後のケースが、本番環境まで生き延びるバグです。double に代入しても助けにはなりません。除算 total / count は代入が起こる前に既に完了しており、既に int だからです。対策は、たいていはキャストを使って、式の内側で片方のオペランドを浮動小数点にすることです。

関連するルールが 2 つあります。整数でゼロ除算するのは未定義動作で、たいていプログラムがクラッシュします。そして INT_MIN / -1 はオーバーフローします。

剰余演算子

% は余りを返し、整数にのみ使えます。

偶数判定の x % 2 == 0 と、配列の添字を巻き戻すための % n が、日常の 2 大用途です。

負の数では、C の %被除数の符号に従います。

したがって x が負になりうるとき、x % 2 == 1 は奇数判定として壊れています。-7 % 2-1 だからです。代わりに x % 2 != 0 を使いましょう。

浮動小数点の余りに % を使うとコンパイルエラーです。math.hfmod() を使ってください。

代入と複合代入

= は値を格納します。複合形は演算と格納を組み合わせます。

x += 5x = x + 5 を意味しますが、x を一度しか評価しません。対象が arr[compute_index()] のようなものであるときに、これが効いてきます。

代入自体も、代入した値を返す式です。だからこそ a = b = c = 0 が動き(右から左に代入されます)、=== の取り違えが黙ってコンパイルされてしまうのです。

インクリメントとデクリメント

++ は 1 を足し、-- は 1 を引きます。それぞれに前置形と後置形があります。

単独の文としては 2 つは交換可能で、for のヘッダーで i++ が慣例なのは、伝統以上の深い理由はありません。

やってはいけないのは、ある式の中で変更される変数を、その式の中で 2 回以上使うことです。

int i = 5;
int x = i++ + i++;      /* 未定義動作 */
arr[i] = i++;           /* 未定義動作 */
printf("%d %d", i++, i);/* 未定義動作 */

C はこれらの部分式が評価される順序を定めていないので、結果が未規定であるにとどまらず、プログラム全体の動作が未定義になります。文を 2 つに分ければ、あいまいさは消えます。

比較演算子

6 つあり、どれも int を生み出します。真なら 1、偽なら 0 です。

結果が専用の真偽型ではなくただの int であることは C を特徴づける性質です。そこから何が導かれるか(=== のバグを含めて)は C の真偽値 を参照してください。

比較の間違いのうち 2 つは C に特有です。数学が示唆するようには連鎖できません。if (1 < x < 10) は常に真になります。1 < x が 0 か 1 に評価され、それが 10 と比較されるからです。if (x > 1 && x < 10) と書きましょう。そして == で文字列を比較するのは、テキストではなくポインタの比較です。string.hstrcmp を使ってください。

論理演算子

&& は AND、|| は OR、! は NOT です。3 つとも、ゼロ以外の値はすべて真として扱います。

これらは短絡します。&& は片方のオペランドが偽になった時点で止まり、|| は片方が真になった時点で止まります。残りは決して評価されません。これは単なる最適化ではなく、安全のために頼ってよい保証です。

この 2 つの検査の順序を入れ替えると、NULL での呼び出しはヌルポインタを参照外ししてクラッシュします。先にガードし、それから使う。&& の連鎖における順序はロジックの一部です。

条件演算子

C で唯一の 3 項演算子は、2 つの値から選びます。

条件 ? 真のときの値 : 偽のときの値 です。式なので、if 文が置けない場所にも収まります。printf の引数の中や、初期化子の中などです。短く保ちましょう。条件演算子をネストさせると、あっという間に読めなくなります。

ビット演算子(簡単に)

これらは整数の個々のビットに対して働きます。

フラグ、マスク、ハードウェアレジスタに使われます。注意が 2 つ。&|&&|| ではありません(短絡せず、ビットごとに働きます)。そして符号付きの値をシフトしたり、型の幅を超えてシフトしたりするのは未定義です。ビット操作には符号なし型を使いましょう。

優先順位

式にかっこがないとき、何が何に結び付くかを決めるのがこの表です。優先順位の高い順に並んでいます。

レベル演算子結合方向
1() [] -> . x++ x--左から右
2! ~ ++x --x +x -x *p &x sizeof (type)右から左
3* / %左から右
4+ -左から右
5<< >>左から右
6< <= > >=左から右
7== !=左から右
8&左から右
9^左から右
10ビット単位の OR左から右
11&&左から右
12論理 OR左から右
13?:右から左
14= += -= *= /= %= など右から左
15,左から右

実際に効いてくる帰結は次のとおりです。

a + b * c       /* a + (b * c) の意味 */
a < b == c < d  /* (a < b) == (c < d) の意味 - 意図どおりであることはまずない */
x & 1 == 0      /* x & (1 == 0)、つまり x & 0 の意味 - 典型的なバグ */
*p++            /* *(p++) の意味 - p を参照外ししてから、p を進める */

3 行目は覚える価値があります。==& より強く結び付くので、ビットマスクの検査にはかっこが要ります。(x & 1) == 0 です。

15 段階すべてを覚えている人はいませんし、その必要もありません。*+ に勝つこと、比較が && に勝ち &&|| に勝つこと、そしてそれ以外にはかっこを付けること。これだけ覚えましょう。

if ((flags & MASK) != 0 && (count > 0 || force)) { ... }

この行には冗長なかっこがありますが、あるほうが良いのです。

よくある質問

C の % は何をしますか?

% は剰余演算子で、整数除算の余りを返します。17 % 5 は 2 です。整数にしか使えません。floatdouble に使うとコンパイルエラーになり、浮動小数点版は math.hfmod() です。

なぜ C では 5 / 2 が 2 になるのですか?

両方のオペランドが整数なので、C は整数除算を行い小数部分を捨てるからです。2.5 を得るには片方を浮動小数点値にします。5 / 2.0 とするか、(double)a / b のように片方をキャストしてください。

i++ と ++i の違いは何ですか?

どちらも i に 1 を加えます。++i(前置)は先にインクリメントして新しい値を返し、i++(後置)は古い値を返してから後でインクリメントします。単独の文としては同じです。違いが効くのは結果を使うときだけで、int b = a++;int b = ++a; の対比がその例です。

C の演算子の優先順位とは何ですか?

かっこのない式で、演算子がどの順に結び付くかという順序です。*/%+- より強く結び付き、それらは比較より、比較は && より、&&|| より、そして最後が代入です。迷ったらかっこを足しましょう。コストはゼロで、疑問が消えます。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める