C言語の基本|優先順位と結合規則のまとめ

迷ったら丸括弧で意図を見える形に。優先順位と結合規則を整理すれば、計算式も条件式も読みやすくなります。

C言語の演算子には、どの演算子が強く結び付くのかを決める優先順位と、同じ優先順位の演算子が並んだときのまとまり方を決める結合規則があります。

式の中に+、*、==、&&、||などが混ざっていると、どの部分が1つのまとまりになるのか分かりにくくなることがあります。

すべての規則を一度に暗記する必要はありません。まずは、計算式、条件式、代入式などでよく使うパターンを確実に読めるようになることが大切です。

ここでは、次の内容を順番に整理します。

  • 丸括弧で計算のまとまりを指定する方法
  • 条件式を読みやすく書き換える方法
  • 左結合と右結合の違い
  • コンマ演算子の働き
  • コンマ演算子と区切りのコンマの違い
  • for文で複数の式を扱う方法

なお、優先順位と結合規則が決めるのは、主に式のまとまり方です。実際の評価順序とは必ずしも同じ意味ではありません。最初は、式がどのように区切られるのかに注目して確認していきましょう。

優先順位と結合規則の違い

優先順位と結合規則には、次のような違いがあります。

規則役割使用例
優先順位異なる種類の演算子が並んだときのまとまり方を決めるa + b * c
結合規則同じ優先順位の演算子が並んだときのまとまり方を決めるa - b + c
丸括弧通常の規則より明確にまとまりを指定する(a + b) * c

a + b * cでは、*が+よりも優先順位が高いため、b * cが先にまとまります。

a + (b * c)

a - b + cでは、-と+の優先順位が同じです。どちらも左結合なので、左側からまとまります。

(a - b) + c

加算を先にまとめたい場合は、丸括弧を使います。

(a + b) * c

丸括弧は計算結果を変えるためだけでなく、式の意図を読み手へ分かりやすく伝えるためにも利用できます。

丸括弧で計算のまとまりを指定する

次の式では、乗算演算子*が加算演算子+よりも優先順位が高くなっています。

int result = 2 + 3 * 4;

この式は、次のようにまとまります。

2 + (3 * 4)
2 + 12
14

2 + 3を先にまとめたい場合は、丸括弧を付けます。

int result = (2 + 3) * 4;

この場合は、次のように計算されます。

(2 + 3) * 4
5 * 4
20

丸括弧の位置によって、結果が14から20へ変わります。

丸括弧の有無による料金の違いを確認するプログラム

ファイル名:6_11_1.c

#include <stdio.h>

int main(void)
{
    int base_price = 120;
    int option_price = 30;
    int sets = 4;
    int total1;
    int total2;

    total1 = base_price + option_price * sets;
    total2 = (base_price + option_price) * sets;

    printf("丸括弧なしの計算結果: %d円\n", total1);
    printf("丸括弧ありの計算結果: %d円\n", total2);
    printf("丸括弧で計算の意図を明確にできます。\n");

    return 0;
}
実行結果の例
丸括弧なしの計算結果: 240円
丸括弧ありの計算結果: 600円
丸括弧で計算の意図を明確にできます。

total1では、option_price * setsが先にまとまります。

total1 = base_price + option_price * sets;

変数の値を当てはめると、次の計算になります。

120 + (30 * 4)
120 + 120
240

total2では、base_price + option_priceを丸括弧で囲んでいます。

total2 = (base_price + option_price) * sets;

丸括弧内が先にまとまるため、次の計算になります。

(120 + 30) * 4
150 * 4
600

基本料金を一度だけ加えて、オプション料金を4セット分計算するならtotal1の式になります。

基本料金とオプション料金を1セット分として、それを4セット計算するならtotal2の式になります。どちらが正しいかは、プログラムで表現したい内容によって決まります。

図1:丸括弧による計算結果の違い

この図から分かること

120 + 30 * 4では、乗算が加算よりも強く結び付くため、30 * 4が先にまとまります。結果は240です。

(120 + 30) * 4では、丸括弧によって加算部分が1つのまとまりになります。結果は600です。

丸括弧を使うと、どの計算を先にまとめたいのかを明確に指定できます。

条件式でも丸括弧を活用する

丸括弧は、数値を計算する式だけでなく、if文などの条件式でも役立ちます。

次の条件式を見てみましょう。

if (a < b && b < c || a == c) {
    /* 条件を満たした場合の処理 */
}

この式では、主に次の演算子が使われています。

種類演算子優先順位
大小比較<高い
等価比較==<より低い
論理AND&&比較演算子より低い
論理OR||&&より低い

優先順位に従うと、この条件式は次のようにまとまります。

if ((a < b && b < c) || a == c) {
    /* 条件を満たした場合の処理 */
}

さらに細かく表すと、次の形です。

if (((a < b) && (b < c)) || (a == c)) {
    /* 条件を満たした場合の処理 */
}

すべての丸括弧が文法上必要なわけではありません。しかし、どの比較結果を&&で結び、その結果を||でどの条件と結んでいるのかが分かりやすくなります。

&&と||が混ざる条件式

&&は||よりも優先順位が高いため、次の式では&&側が1つのまとまりになります。

condition1 && condition2 || condition3

式のまとまりは次のとおりです。

(condition1 && condition2) || condition3

この条件が真になるのは、次のいずれかの場合です。

  • condition1とcondition2の両方が真
  • condition3が真

一方、condition1が真であり、さらにcondition2またはcondition3のどちらかが真という条件にしたい場合は、次のように書きます。

condition1 && (condition2 || condition3)

丸括弧の位置によって、条件式の意味が変わります。

条件式意味
(condition1 && condition2) || condition3condition1とcondition2が真、またはcondition3が真
condition1 && (condition2 || condition3)condition1が真で、condition2またはcondition3が真

複数の論理演算子が混ざる条件式では、通常の優先順位に任せるだけでなく、丸括弧で意図を示すと読みやすくなります。

読みやすさのために丸括弧を付ける

丸括弧は、計算結果を変える目的だけで使うものではありません。通常の優先順位と同じまとまりであっても、式を読みやすくするために利用できます。

丸括弧を付けるときは、次の点を意識しましょう。

  • 複数の比較を論理演算子で結ぶ場合は、条件ごとのまとまりを示す
  • &&と||が混ざる場合は、どちらを1つの条件として扱うのか示す
  • 通常の優先順位と異なる計算をしたい場合は、先にまとめる部分を囲む
  • 読み手が迷いそうな式には、通常の優先順位と同じでも丸括弧を付ける
  • 丸括弧を重ねすぎて、対応関係が分かりにくくならないようにする

丸括弧を適切に付けておくと、後からプログラムを読み返したときにも、式の意図を理解しやすくなります。

結合規則をおさらいする

結合規則は、同じ優先順位の演算子が並んだとき、式がどちら側からまとまるかを決める規則です。

種類使用例まとまる向き式のまとまり
左結合10 - 5 + 2左から右(10 - 5) + 2
左結合24 / 6 * 2左から右(24 / 6) * 2
右結合a = b = c = 5右から左a = (b = (c = 5))

加算、減算、乗算、除算など、多くの二項演算子は左結合です。

次の式では、-と+が同じ優先順位で、どちらも左結合です。

10 - 5 + 2

式は次のようにまとまります。

(10 - 5) + 2
5 + 2
7

一方、代入演算子=は右結合です。

a = b = c = 5;

この式は次のようにまとまります。

a = (b = (c = 5))

代入の流れは次のようになります。

cへ5を代入
↓
bへ5を代入
↓
aへ5を代入

C言語の代入式は、代入した値を式の値として持ちます。そのため、cへ代入した5をbへ渡し、さらにaへ渡せます。

結合規則と評価順序の違い

結合規則は、式のグループ化を決めます。

たとえば、10 - 5 + 2が左結合であるという説明は、次のまとまりになることを意味します。

(10 - 5) + 2

一般的には左から計算すると説明されることもありますが、結合規則が直接決めるのは式のまとまり方です。

関数呼び出しや、値を書き換える演算を複数含む式では、実際の評価順序が別の規則によって決まる場合があります。そのため、複雑な式を1行に詰め込みすぎないことも大切です。

図2:条件式と結合規則の読み方

この図から分かること

&&は||よりも優先順位が高いため、条件式ではa < b && b < cが1つのまとまりになります。

-と+は同じ優先順位で左結合なので、10 - 5 + 2は(10 - 5) + 2というまとまりになります。

代入演算子は右結合なので、a = b = c = 5は最も右側のcへの代入から外側へまとまります。

式を読むときの確認手順

複数の演算子を含む式を見たときは、次の順番で確認すると読みやすくなります。

  1. 最初に丸括弧で囲まれた部分を確認する
  2. 異なる演算子がある場合は優先順位を確認する
  3. 同じ優先順位の演算子が続く場合は結合規則を確認する
  4. 条件式では比較ごとのまとまりを確認する
  5. &&と||が混ざっている場合は、&&側のまとまりを確認する
  6. 読みにくい場合は、丸括弧を補って書き直す

たとえば、次の条件式を確認します。

a < b && b < c || a == c

比較演算子による3つの比較を確認します。

a < b
b < c
a == c

次に、&&が||よりも高い優先順位を持つことを確認します。

(a < b && b < c) || a == c

最後に、読みやすさを考えて丸括弧を補います。

((a < b) && (b < c)) || (a == c)

このように、式を小さなまとまりへ分けると、複雑な条件も読み解きやすくなります。

コンマ演算子の働き

C言語には、コンマを演算子として使うコンマ演算子があります。

コンマ演算子には、次の特徴があります。

  • 左側の式を先に評価する
  • 左側の式の値はコンマ式全体の値としては使わない
  • 左側の処理を完了してから右側の式を評価する
  • 右側の式の値をコンマ式全体の値とする
  • 優先順位は非常に低い

次の式を見てみましょう。

result = (first = 12, second = 15);

処理の流れは次のとおりです。

firstへ12を代入
↓
secondへ15を代入
↓
コンマ式全体の値は15
↓
resultへ15を代入

コンマ演算子は代入演算子よりも優先順位が低いため、この例では丸括弧が重要です。丸括弧によって、first = 12, second = 15の全体をresultへ代入する式としてまとめています。

コンマ演算子が最後の式の値を返すことを確認するプログラム

ファイル名:6_11_2.c

#include <stdio.h>

int main(void)
{
    int first = 3;
    int second = 4;
    int result;

    result = (first = 12, second = 15);

    printf("first=%d\n", first);
    printf("second=%d\n", second);
    printf("result=%d\n", result);
    printf("コンマ演算子は最後の式の値を返します。\n");

    return 0;
}
実行結果の例
first=12
second=15
result=15
コンマ演算子は最後の式の値を返します。

次の宣言に含まれるコンマは、変数の宣言を区切るための記号です。

int first = 3, second = 4, result;

このコンマはコンマ演算子ではありません。

一方、次の丸括弧内にあるコンマはコンマ演算子です。

result = (first = 12, second = 15);

最初にfirstへ12が代入され、次にsecondへ15が代入されます。右側にあるsecond = 15の値がコンマ式全体の値になるため、resultにも15が代入されます。

コンマ演算子と区切りのコンマ

C言語のコンマには複数の役割があります。コンマが書かれているからといって、すべてがコンマ演算子になるわけではありません。

使用場所コンマの役割
式の中コンマ演算子result = (first = 12, second = 15);
変数宣言宣言子の区切りint first, second, result;
関数呼び出し引数の区切りprintf("%d", result);
文字列の中表示する普通の文字"first=%d, second=%d"
for文の初期化部複数の式をつなぐコンマ演算子left = 1, right = 7
for文の更新部複数の式をつなぐコンマ演算子left++, right--

コンマ演算子と区切りのコンマは、書かれている場所から見分けます。

for文で使うコンマ演算子

for文では、初期化部や更新部に複数の式を記述したい場合があります。

for (left = 1, right = 7;
     left <= right;
     left++, right--)

for文の丸括弧内は、セミコロンによって次の3つに分かれています。

部分記述役割
初期化部left = 1, right = 72つの変数へ初期値を代入する
条件式left <= right繰り返しを続けるか判定する
更新部left++, right--leftを増やし、rightを減らす

初期化部では、leftへ1を代入した後でrightへ7を代入します。

更新部では、leftを1増やした後でrightを1減らします。

for文で2つの変数を同時に変化させるプログラム

ファイル名:6_11_3.c

#include <stdio.h>

int main(void)
{
    int left;
    int right;

    for (left = 1, right = 7;
         left <= right;
         left++, right--) {
        printf("left=%d, right=%d\n", left, right);
    }

    return 0;
}
実行結果の例
left=1, right=7
left=2, right=6
left=3, right=5
left=4, right=4

最初に、初期化部が一度だけ実行されます。

left = 1, right = 7

leftへ1を代入した後、rightへ7を代入します。

次に、条件式left <= rightを確認します。最初は1 <= 7なので、ループ内のprintfが実行されます。

ループ内の処理が終わると、更新部が実行されます。

left++, right--

leftは1ずつ増え、rightは1ずつ減ります。値の変化は次のとおりです。

判定leftrightleft <= right
1回目17
2回目26
3回目35
4回目44
次の判定53

leftが5、rightが3になると、left <= rightが偽になるため、繰り返しを終了します。

printfで使われるコンマの役割

for文の中では、次のprintfを実行しています。

printf("left=%d, right=%d\n", left, right);

この1行には、役割の異なるコンマが含まれています。

文字列内のコンマは、画面へ表示される普通の文字です。

left=%d, right=%d

書式文字列、left、rightの間にあるコンマは、printfへ渡す引数を区切るための記号です。

printf("left=%d, right=%d\n", left, right);

引数を区切るコンマはコンマ演算子ではありません。そのため、最後の引数の値をprintf全体の値として返すという働きはありません。

また、引数を区切るコンマは、引数が左から右へ評価されることも保証しません。コンマ演算子とは異なるものとして区別しましょう。

図3:コンマ演算子と区切りのコンマ

この図から分かること

式の中で使われるコンマ演算子は、左側の式を評価してから右側の式を評価し、右側の式の値をコンマ式全体の値とします。

for文の初期化部や更新部では、複数の式を順番に実行するためにコンマ演算子がよく使われます。

変数宣言や関数呼び出しで使われるコンマは、項目を分けるための区切り記号です。コンマ演算子のように右側の値を全体の値とする働きはありません。

コンマ演算子は複数の処理を1つの式にまとめられますが、使いすぎると処理の流れを追いにくくなります。学習中は、for文の初期化部や更新部など、役割が分かりやすい場面を中心に使うとよいでしょう。