記事内にはプロモーションが含まれています

【C++】演算子の優先順位と結合規則一覧表!間違いやすい計算順序を整理

【C++】演算子の優先順位と結合規則一覧表!間違いやすい計算順序を整理 C++

C++でプログラミングをしていると、「計算式を書いたけれど、思った通りの結果にならない」「条件式が意図しない挙動をする」といった問題に直面することがあります。

その原因の多くは、「演算子の優先順位」の理解不足にあります。

数学で「掛け算・割り算は、足し算・引き算より先に計算する」というルールがあるように、C++の豊富な演算子にも厳格な順序ルールが存在します。

特にC++はポインタやビット演算など、他の言語よりも演算子の種類が多いため、このルールを把握しておくことはバグのないコードを書くために必須のスキルです。

この記事では、C++における演算子の優先順位と結合規則を一覧表で整理し、特に間違いやすいポイントや、可読性を高めるためのテクニックを最新仕様に基づいて解説します。

【本記事の信頼性】
プロフィール
執筆者:マヒロ
  • 執筆者は元エンジニア
  • SES⇒大手の社内SE⇒独立
  • 現在はこじんまりとしたプログラミングスクールを運営
  • モットーは「利他の精神」

C++演算子の優先順位・結合規則 一覧表

まずは、演算子の優先順位を一覧で確認しましょう。
上にあるものほど優先順位が高く(先に計算される)、下にあるものほど低くなります。

また、「結合規則」は、同じ優先順位の演算子が並んだ時に「左から計算するか(左結合)」「右から計算するか(右結合)」を示しています。

優先順位 演算子 説明 結合規則
1 (最高) :: スコープ解決
2 () [] . -> ++ -- 関数呼び出し、添字、メンバアクセス、後置増減
3 ! ~ + - ++ -- * & (type) sizeof new delete

 

static_cast

論理否定、ビット否定、単項プラスマイナス、前置増減、デリファレンス、アドレス、キャスト(C++キャスト含む)、サイズ、動的確保
4 .* ->* メンバポインタアクセス
5 * / % 乗算、除算、剰余
6 + - 加算、減算
7 << >> ビットシフト
8 <=> 三方比較 (C++20 Spaceship) なし(連鎖不可)
9 < > <= >= 関係演算子(比較)
10 == != 等価、不等価
11 & ビット論理積 (AND)
12 ^ ビット排他的論理和 (XOR)
13 | ビット論理和 (OR) 
14 && 論理積 (AND)
15 || 論理和 (OR)
16 ?: 条件演算子 (三項演算子)
17 = += -= *= /= 代入演算子
18 (最低) , カンマ演算子

この表をすべて暗記する必要はありませんが、「代入は最後」「論理演算(&&, ||)は比較(==, <)より後」「ビット演算は比較より後(要注意!)」といった大まかな関係性は覚えておくと役立ちます。

初心者がハマりやすい!間違いやすい優先順位の事例

一覧表だけではイメージしにくい部分について、実際にバグを生みやすいケースをコード付きで解説します。

1. 論理演算子 && と || の優先順位

論理積 &&(かつ)は、論理和 ||(または)よりも優先順位が高いです。

これを意識しないと、複合条件を書いた時に意図しない判定が行われてしまいます。

#include <iostream>

int main() {
    bool a = true;
    bool b = false;
    bool c = true;

    // 意図: (a || b) && c  -> (true または false) かつ true -> true を期待
    // 実際: a || (b && c)  -> true または (false かつ true) -> true
    // このケースではたまたま同じ結果ですが、論理的には異なります。
    
    if (a || b && c) {
        std::cout << "式1: true" << std::endl;
    }

    // 優先順位を明確にするためにカッコをつける
    if ((a || b) && c) {
        std::cout << "式2: true" << std::endl;
    }
    
    return 0;
}

実行結果

式1: true
式2: true

a || b && c と書くと、&& が先に評価されるため、a || (b && c) と解釈されます。

「AまたはB」のどちらかが成立していて、かつ「Cも成立している」という条件を書きたい場合は、必ず (a || b) && c のようにカッコで囲む必要があります。

2. ビット演算子と関係演算子(比較)の罠

C++で最も間違いやすいのが、ビット演算子(&, |, ^)と関係演算子(==, !=, < など)の優先順位です。

関係演算子の方がビット演算子よりも優先順位が高いため、カッコを忘れると全く違う意味になります。

#include <iostream>

int main() {
    int flags = 0x0F; // 0000 1111
    int mask  = 0x01; // 0000 0001

    // 意図: (flags & mask) == mask -> ビットが立っているか判定したい
    // 実際: flags & (mask == mask) -> flags & 1 -> 演算結果になってしまう
    
    // 警告が出ることもありますが、文法的には有効なためバグになりやすい
    if (flags & mask == mask) {
        std::cout << "判定OK?(間違い)" << std::endl;
    } else {
        std::cout << "判定NG(間違い)" << std::endl;
    }

    // 正しい書き方
    if ((flags & mask) == mask) {
        std::cout << "判定OK(正解)" << std::endl;
    }

    return 0;
}

実行結果

判定NG(間違い)
判定OK(正解)

flags & mask == mask は、mask == mask(つまり true1)が先に評価され、その結果と flags のビット積(AND)をとることになります。

ビットフラグの判定を行う際は、必ず (flags & mask) のようにカッコで囲みましょう。

3. ポインタのインクリメント (*p++)

ポインタ操作における *p++ も、初心者を悩ませるポイントです。

後置インクリメント ++ は、デリファレンス(中身を取り出す) * よりも優先順位が高いですが、作用するタイミングや対象が重要です。

#include <iostream>

int main() {
    int arr[] = {10, 20, 30};
    int* p = arr;

    // *p++ の挙動
    // 1. p++ が評価される(ポインタが一つ進む)が、式の結果としては「進む前のp」が返る
    // 2. その「進む前のp」に対して *(デリファレンス)が適用される
    int value = *p++;

    std::cout << "取り出した値: " << value << std::endl; // 10
    std::cout << "現在の*p: " << *p << std::endl;       // 20

    return 0;
}

実行結果

取り出した値: 10
現在の*p: 20

*p++ は「pが指す値を取得し、その後でpのアドレスを進める」という動作になります(配列の走査などでよく使われます)。

もし「pが指す値をインクリメントしたい(10を11にしたい)」場合は、(*p)++ と書く必要があります。

「結合規則」とは?右結合と左結合の違い

優先順位が同じ演算子が並んだ場合、どちらから計算するかを決めるのが「結合規則」です。

基本は「左から右(左結合)」ですが、代入演算子などは「右から左(右結合)」です。

代入演算子の右結合

int a, b, c;
a = b = c = 10;

これは a = (b = (c = 10)) と解釈されます。

まず c に10が代入され、その結果(10)が b に代入され、さらに a に代入されます。

これにより、複数の変数を一度に初期化できます。

優先順位に迷ったら「カッコ」を使おう

ここまで優先順位について解説してきましたが、実務において最も重要なアドバイスは「迷ったらカッコ () を付ける」ということです。

自分自身が優先順位を完璧に覚えていたとしても、コードを読む他のメンバーが覚えているとは限りません。

a + b * c は誰でも分かりますが、a & b == ca || b && c は誤解を招く可能性があります。

// どちらも同じ意味だが、下の方が圧倒的に読みやすい
if (x & mask == 0)   { ... } // 危険
if ((x & mask) == 0) { ... } // 安全・明確

カッコを付けることによる実行速度の低下はありません(コンパイラが最適化します)。

可読性とバグ防止のために、積極的なカッコの使用を推奨します。

演算子のオーバーロードと優先順位

C++では、クラスや構造体に対して演算子の意味を独自に定義できる「演算子のオーバーロード」機能があります。

しかし、演算子の優先順位や結合規則を変更することはできません

例えば、+ 演算子をオーバーロードして「掛け算のような処理」を実装したとしても、優先順位はあくまで +(加算)のままです。

直感に反する挙動を避けるため、オーバーロードする際は、元の演算子が持つ意味や優先順位のイメージを壊さないように設計することが大切です。

C++のスキルを活かして年収を上げる方法

以上、C++の「演算子の優先順位」と「結合規則一覧表」などについて解説しました。

なお、C++のスキルがある場合には、「転職して年収をアップさせる」「副業で稼ぐ」といった方法を検討するのがおすすめです。

C++を扱えるエンジニアは希少価値が高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。

なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。

今すぐ転職する気がなくとも、とりあえず転職エージェントに無料登録しておくだけで、スカウトが届いたり、思わぬ好待遇の求人情報が送られてきたりするというメリットがあります。

併せて、副業案件を獲得できるエージェントにも登録しておくと、空いている時間を活かして稼げるようなC++の案件を探しやすくなります。

転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。