C++の演算子の優先順位とは、式の中に複数の演算子があるときに、どの演算子がどの範囲に結びつくかを決めるルールです。
たとえば、足し算と掛け算が同じ式に含まれている場合、掛け算のほうが足し算よりも優先順位が高いため、掛け算が先にまとまります。
ただし、ここで注意したいのは、演算子の優先順位は「どの演算を先に実行するか」を必ず決めるものではない、という点です。
優先順位が決めるのは、あくまで式の構文上のまとまりです。
C++では、演算子の優先順位、結合方向、評価順序を分けて理解することが重要です。
優先順位・結合方向・評価順序の違い
優先順位とは
優先順位は、複数の演算子が並んだときに、どの演算子が強く結びつくかを決めるルールです。
掛け算、割り算、剰余演算は、足し算や引き算よりも優先順位が高くなります。
そのため、足し算と掛け算が混ざった式では、掛け算の部分が先にひとまとまりとして扱われます。
ただし、複雑な式では、優先順位に頼りすぎると読みづらくなります。
実務では、意図を明確にするために括弧を使うのが安全です。
結合方向とは
結合方向とは、同じ優先順位の演算子が並んだときに、左から結びつくのか、右から結びつくのかを決めるルールです。
たとえば、引き算は左結合です。
複数の引き算が並んだ場合、左側から順にまとまります。
一方、代入演算子は右結合です。
複数の代入が並んだ場合、右側からまとまります。
このように、同じ優先順位の演算子であっても、結合方向によって式の解釈が変わることがあります。
評価順序とは
評価順序とは、式の中の各部分が実際にどの順番で評価されるかを表すルールです。
ここが非常に重要です。演算子の優先順位と評価順序は同じではありません。
優先順位は、式の構文上のまとまりを決めるものです。
一方、評価順序は、実際にどの部分式が先に評価されるかを決めるものです。
たとえば、関数呼び出しが複数含まれる式では、優先順位によって式のまとまりは決まっていても、関数が呼び出される順番までは必ずしも決まりません。
そのため、副作用のある処理を1つの式に詰め込みすぎると、意図しない動作や未定義動作につながる可能性があります。
C++の演算子優先順位の全体像
最も優先順位が高いもの
最も優先順位が高いのは、スコープ解決演算子です。
これは、名前空間やクラスの中にある名前を指定するときに使われます。
標準ライブラリの名前空間や、クラスの静的メンバ、ネストされた型などを指定する場面で使われます。
後置演算子
次に優先順位が高いのが、後置演算子です。
後置インクリメント、後置デクリメント、関数呼び出し、配列の添字アクセス、メンバアクセスなどがここに含まれます。
このグループは非常に優先順位が高いため、ポインタやイテレータを扱うときには特に注意が必要です。
たとえば、後置インクリメントと間接参照が組み合わさる式では、後置インクリメントのほうが強く結びつきます。
そのため、「ポインタを進める」のか「ポインタが指す値を変更する」のかを明確に区別する必要があります。
単項演算子
単項演算子には、前置インクリメント、前置デクリメント、単項プラス、単項マイナス、論理否定、ビット反転、間接参照、アドレス取得、サイズ取得、メモリ確保、メモリ解放などが含まれます。
単項演算子は右結合です。
ポインタ操作、アドレス操作、符号反転、論理否定など、C++では頻繁に使われるため、優先順位の位置を理解しておくと式の読み間違いを防げます。
メンバへのポインタ演算子
メンバへのポインタ演算子は、C++特有の演算子です。
一般的なアプリケーション開発では頻繁に使うものではありませんが、クラスのメンバをポインタとして扱う場合に登場します。
優先順位としては、単項演算子より低く、乗算や除算より高い位置にあります。
乗算・除算・剰余演算
乗算、除算、剰余演算は、足し算や引き算よりも優先順位が高いです。
算術演算では最も基本的なルールの1つです。
数学での計算順序と同じ感覚で理解できます。
加算・減算
加算と減算は、乗算や除算よりも優先順位が低く、シフト演算子よりは高い位置にあります。
同じ優先順位の演算子が並んだ場合は、左から右へ結合します。
シフト演算子
シフト演算子には、左シフトと右シフトがあります。
ビット演算で使われるほか、C++ではストリーム入出力にも使われます。
この演算子は、加算や減算よりも優先順位が低く、比較演算子よりも高いです。
そのため、出力ストリームと比較演算を混ぜる場合には注意が必要です。
比較結果を出力したい場合は、比較式を括弧で囲んだほうが安全です。
三方比較演算子
C++20では、三方比較演算子が導入されました。
これは、2つの値の大小関係をまとめて比較するための演算子です。
通常の大小比較演算子よりも高い優先順位を持ちます。
C++20以降のコードを読む場合は、この演算子の位置も知っておくとよいです。
大小比較演算子
大小比較演算子には、小なり、小なりイコール、大なり、大なりイコールがあります。
これらは、加算やシフト演算子よりも優先順位が低く、等価比較演算子よりは高い位置にあります。
等価比較演算子
等価比較演算子には、等しいかどうかを調べる演算子と、等しくないかどうかを調べる演算子があります。
大小比較演算子よりも優先順位が低く、ビットANDよりは高いです。
この位置関係は重要です。
比較演算とビット演算を混ぜると、意図しない解釈になることがあります。
ビットAND
ビットANDは、ビット単位でAND演算を行う演算子です。
論理ANDとは別物です。
特に注意すべきなのは、ビットANDは等価比較よりも優先順位が低いという点です。
そのため、ビットマスクの結果を比較したい場合は、ビットANDの部分を括弧で囲む必要があります。
ビットXOR
ビットXORは、ビット単位で排他的ORを行う演算子です。
ビットANDよりも優先順位が低く、ビットORよりも高い位置にあります。
ビットOR
ビットORは、ビット単位でOR演算を行う演算子です。
論理ORとは異なります。
ビット演算子は、比較演算子や論理演算子と混ざると読み間違いが起きやすいため、実務では括弧を使って意図を明確にするのが基本です。
論理AND
論理ANDは、条件が両方とも真であるかを判定する演算子です。
論理ANDは短絡評価を行います。
つまり、左側の条件が偽であれば、右側の条件は評価されません。
この性質は、ポインタがヌルでないことを確認してからメンバにアクセスする場合などによく使われます。
論理OR
論理ORは、どちらか一方でも真であれば真になる演算子です。
論理ORも短絡評価を行います。
左側の条件が真であれば、右側の条件は評価されません。
論理ANDと論理ORが混ざる場合、論理ANDのほうが優先順位が高くなります。
ただし、条件式が複雑になる場合は、括弧を使ったほうが読みやすくなります。
三項条件演算子・代入演算子
三項条件演算子、代入演算子、複合代入演算子、例外送出、コルーチン関連の演算子は、かなり低い優先順位にあります。
ここで重要なのは、C++では三項条件演算子と代入演算子が同じ優先順位グループに属しているという点です。
これらは右結合です。
そのため、複数の代入が並ぶ場合は右側から結びつきます。
三項条件演算子は便利ですが、入れ子にすると非常に読みづらくなります。
複雑な条件分岐では、無理に1つの式にまとめず、通常の条件分岐に分けたほうがよい場合もあります。
カンマ演算子
カンマ演算子は、C++の演算子の中で最も優先順位が低い演算子です。
左側の式を評価し、その後に右側の式を評価し、式全体としては右側の値を返します。
ただし、関数呼び出しの引数を区切るカンマとは別物です。
見た目は同じですが、文法上の意味は異なります。
正確な優先順位の流れ
高い順に見た大まかな並び
C++の演算子優先順位は、高い順に大まかに整理すると次の流れになります。
最も高いのがスコープ解決演算子です。
その次に、後置インクリメント、後置デクリメント、関数呼び出し、配列アクセス、メンバアクセスなどの後置演算子が来ます。
その下に、前置インクリメント、前置デクリメント、論理否定、間接参照、アドレス取得、サイズ取得、メモリ確保、メモリ解放などの単項演算子があります。
さらにその下に、メンバへのポインタ演算子、乗算・除算・剰余演算、加算・減算、シフト演算子、比較演算子、等価比較演算子が続きます。
その後、ビットAND、ビットXOR、ビットOR、論理AND、論理ORの順になります。
最後に、三項条件演算子、代入演算子、カンマ演算子が続きます。
実務で特に覚えるべき順序
実務では、すべてを丸暗記する必要はありません。
特に重要なのは、乗算や除算は加算や減算より強いこと、比較演算はビット演算より強いこと、論理ANDは論理ORより強いこと、代入はかなり弱いこと、カンマ演算子は最も弱いことです。
また、後置演算子は非常に強いという点も重要です。
ポインタやイテレータを扱う場合、この点を理解していないと式を読み間違える可能性があります。
よくある間違い
ビット演算と比較演算の混同
C++で非常によくある間違いが、ビットANDと等価比較を括弧なしで組み合わせるケースです。
ビットマスクを使って特定のフラグが立っているかどうかを調べたい場合、ビットANDの結果を比較する必要があります。
しかし、等価比較演算子はビットANDよりも優先順位が高いため、括弧を書かないと、先に比較が結びついてしまいます。
そのため、ビット演算の結果を比較するときは、ビット演算の部分を必ず括弧で囲むのが安全です。
出力演算子と比較演算の混同
ストリーム出力に使う左シフト演算子は、等価比較演算子よりも優先順位が高いです。
そのため、比較結果を出力したい場合に括弧を書かないと、出力処理と比較処理が意図しない形で結びつくことがあります。
比較結果を出力する場合は、比較式全体を括弧で囲むと安全です。
ポインタと後置インクリメントの混同
ポインタの間接参照と後置インクリメントを組み合わせる式も、よく誤解されます。
後置インクリメントは単項の間接参照よりも優先順位が高いため、ポインタそのものを進めるのか、ポインタが指している値を増やすのかを明確に区別する必要があります。
この違いは、配列、イテレータ、ポインタを扱うC++コードでは非常に重要です。
論理ANDと論理ORの混在
論理ANDは論理ORよりも優先順位が高いです。
そのため、複数の条件を組み合わせる場合、論理ANDの部分が先にまとまります。
ただし、条件式はコードの意図を伝える重要な部分です。
たとえ優先順位を正しく理解していても、読みやすさのために括弧を使うことをおすすめします。
代入と比較の混同
代入演算子は、比較演算子よりも優先順位が低いです。
そのため、代入した値を比較したい場合は、代入部分を括弧で囲む必要があります。
これは、入力処理やループ条件でよく登場するパターンです。
括弧がないと、比較結果が代入されてしまい、意図した値が変数に入らないことがあります。
優先順位と評価順序に関する注意点
優先順位は実行順序ではない
C++を学ぶうえで特に重要なのが、優先順位と評価順序を混同しないことです。
優先順位は、式がどのように構文解析されるかを決めます。
一方で、評価順序は、実際にどの部分式が先に評価されるかを決めます。
この2つは別の概念です。
たとえば、演算子の優先順位によって式のまとまりが決まっていても、その式の中にある関数呼び出しの順番まで必ず決まるわけではありません。
副作用のある式は分けるべき
インクリメント、代入、関数呼び出しによる状態変更など、副作用のある処理を1つの式に複数入れると、読みづらくなるだけでなく、未定義動作や意図しない挙動につながることがあります。
特に、同じ変数を同じ式の中で複数回変更するような書き方は避けるべきです。
安全で読みやすいコードを書くには、副作用のある処理を別々の文に分けるのが基本です。
C++17以降で規定された評価順序もある
C++では長い間、評価順序が未規定の場面が多くありました。
ただし、C++17以降では一部の評価順序が明確化されています。
たとえば、添字演算、シフト演算、代入演算などでは、特定の評価順序が規定されています。
とはいえ、すべての式で評価順序が直感どおりに決まるわけではありません。
優先順位を理解していても、評価順序まで安易に推測しないことが重要です。
名前付きキャストやtypeidの扱い
通常の優先順位表には含めないほうが正確
C++には、静的キャスト、動的キャスト、再解釈キャスト、const性を外すキャスト、型情報を取得する式などがあります。
これらはC++の式として重要ですが、通常の演算子優先順位表には含めないほうが正確です。
理由は、これらが通常の二項演算子や単項演算子のように、ほかの演算子と優先順位で曖昧に競合するものではないからです。
学習時には、これらを優先順位表の中に無理に入れて覚えるよりも、独立した構文として理解したほうがよいです。
関数形式キャストは後置演算子側に含まれる
一方で、関数形式の型変換は、優先順位表では後置演算子のグループに含まれます。
この点は、名前付きキャストとは分けて理解すると正確です。
正確性の観点で注意すべきポイント
三項条件演算子と代入演算子は同じ優先順位グループ
三項条件演算子は、代入演算子よりも単純に高い優先順位を持つわけではありません。
C++では、三項条件演算子と代入演算子は同じ優先順位グループに属します。
そして、どちらも右結合です。
そのため、「三項演算子は代入より強い」と覚えるのは正確ではありません。
より正確には、「三項条件演算子と代入演算子は同じ優先順位グループで、右結合である」と理解するべきです。
三項条件演算子の中央の式には特殊ルールがある
三項条件演算子には、中央の式が特殊に扱われるというルールがあります。
中央の式は、優先順位に関係なく、あたかも括弧で囲まれているように扱われます。
このため、三項条件演算子は単純な優先順位だけでは説明しきれない部分があります。
ただし、実務では三項条件演算子を複雑にしすぎないことが大切です。
読みづらくなる場合は、通常の条件分岐に分けたほうが安全です。
同じ変数を同じ式で複数回変更しない
同じ変数に対して、同じ式の中で複数回インクリメントや代入を行う書き方は避けるべきです。
これは単に読みづらいだけでなく、未定義動作になることがあります。
特に、加算演算子の左右に同じ変数のインクリメントを置くような書き方は危険です。
C++では、式を短くすることよりも、評価順序と副作用が明確なコードを書くことが重要です。
実務での書き方の指針
迷ったら括弧を書く
C++の演算子優先順位は細かく、多くの演算子があります。
すべてを完全に暗記していても、他の人が読んだときにすぐ理解できるとは限りません。
そのため、ビット演算、比較演算、論理演算、三項条件演算子、代入演算子が混ざる場合は、積極的に括弧を使うべきです。
括弧を書くことで、コンパイラに対してだけでなく、人間に対しても意図が明確になります。
複雑な条件式は分ける
条件式が長くなりすぎる場合は、1つの式にまとめるよりも、意味のある名前を持つ変数に分けたほうが読みやすくなります。
特に、複数のビット判定、複数の論理条件、ヌルチェック、範囲チェックなどが混ざる場合は、式を分割することでバグを防ぎやすくなります。
副作用は1つの文に詰め込まない
インクリメント、代入、関数呼び出しによる状態変更などは、1つの式の中に詰め込みすぎないほうが安全です。
C++では、短く書けることよりも、評価順序が明確であることのほうが重要です。
副作用のある処理は、できるだけ別々の文に分けると、コードの意図が明確になります。
まとめ
C++の演算子優先順位で重要なこと
C++の演算子優先順位は、式の構文上のまとまりを決めるルールです。
ただし、優先順位は実行順序そのものではありません。評価順序とは別の概念です。
また、同じ優先順位の演算子が並んだ場合には、結合方向によって式のまとまりが決まります。
特に注意すべき演算子
実務で特に注意すべきなのは、ビット演算、比較演算、論理演算、三項条件演算子、代入演算子、カンマ演算子です。
これらは優先順位を誤解しやすく、括弧なしで書くと意図しない解釈になりやすいです。
また、後置インクリメントとポインタの間接参照の組み合わせも、C++でよくある落とし穴です。
正確に理解すべきポイント
三項条件演算子と代入演算子は同じ優先順位グループに属し、右結合です。
名前付きキャストや型情報取得の式は、通常の演算子優先順位表には含めないほうが正確です。
同じ変数を同じ式の中で複数回変更するような書き方は、未定義動作になることがあるため避けるべきです。
実務的な結論
C++では、演算子の優先順位を理解することは重要です。
しかし、それ以上に重要なのは、読みやすく、誤解されにくい式を書くことです。
優先順位に頼りすぎず、必要なところでは括弧を使い、副作用のある処理は分ける。
これが、C++で安全かつ保守しやすいコードを書くための基本です。
以上、C++の演算子の優先順位についてでした。
最後までお読みいただき、ありがとうございました。
