こんにちは、ナナです。
皆さんにとって一番身近な演算子は「四則演算(+-×÷)」ですが、プログラミング言語には他にもたくさんの「演算子」が用意されています。
C言語の「演算子」にはどのような種類があるのか、優先順位とは何かを解説していきましょう。
本記事では次の疑問点を解消する内容となっています。
それでは、「演算子」の種類と優先順位について学んでいきましょう。
演算子の種類と優先順位
まずは、C言語で使用できる演算子と優先順位を紹介しましょう。
演算子の一覧
表の上に位置するほど、優先順位が高くなります。
優先順位 | 演算子 | 名称 | 使い方 |
---|---|---|---|
1(高) | () [] . -> ++(後置) ーー(後置) | 関数呼び出し 添字演算子 ドット演算子 アロー演算子 インクリメント(後置) デクリメント(後置) | func() num[0] pos.x pPos->x num++ num– |
2 | + – & * sizeof ! ~ ++ ーー | 正符号 負符号 アドレス演算子 間接参照演算子 サイズオブ演算子 論理否定 ビットの論理否定 インクリメント(前置) デクリメント(前置) | +num -num &data *pdata sizeof(num) !num ~num ++num –num |
3 | (型名) | キャスト | (long)num |
4 | * / % | 乗算 徐算 剰余 | num1 * num2 num1 / num2 num1 % num2 |
5 | + – | 加算 減算 | num1 + num2 num1 – num2 |
6 | << >> | 左シフト 右シフト | num << 5 num >> 5 |
7 | < <= > >= | 小なり 小なりイコール 大なり 大なりイコール | num < 5 num <= 5 num > 5 num >= 5 |
8 | == != | イコール ノットイコール | num == 5 num != 5 |
9 | & | AND演算子 | num & 0x7F |
10 | ^ | XOR演算子(排他的論理和) | num ^ 0x80 |
11 | | | OR演算子 | num | 0x80 |
12 | && | 論理積 | num1 == 0 && num2 == 0 |
13 | || | 論理和 | num1 == 0 || num2 == 0 |
14 | ?: | 三項演算子 | num % 2 ? 1 : 0 |
15 | = +=など | 代入 自己代入形式の代入 | num = 1 num += 1 |
16(低) | , | カンマ | num1, num2 |
加算(+)と乗算(*)では、乗算の方がより優先順位が高くなっているのがわかりますね。
演算子の種類はたくさんありますが、C言語初心者の方はカリキュラムを進めて順に覚えていけば大丈夫です。
優先順位に関しては全てを覚える必要はありません。ポイントとなる関係性だけは知っておくとよいでしょう。
演算子の優先順位の役割とは?
「演算子の優先順位」とは、複数の演算子が同時に登場した場合の、演算される順番を決めるためのものです。
皆さんは算数を習ったときに、掛け算・割り算は足し算・引き算よりも先に計算されると習いましたね。これが「演算子の優先順位」です。
このように複数の演算子が登場した場合は、優先順位の高さに従って計算がされます。これはプログラミングの世界も同じなのです。
それでは、5+2を先に計算をしたい場合はどうすればよいのでしょうか?
このように、括弧を付けることで優先順位を高くするのですね。プログラムの世界でも、このルールは同じです。
では、実際にプログラムで確認してみましょう。
#include <stdio.h>
int main(void)
{
printf("%d\n", 5 + 2 * 3);
printf("%d\n", (5 + 2) * 3);
return 0;
}
11
21
演算子の優先順位に不安を感じたときは、「調べる」「括弧で括る」ということを必ずしましょう。
算数での演算子に優先順位があることは理解しているはずなのに、プログラミングの世界になるとそのルールを忘れてしまう方がいます。
次は私が普段記憶している「知っておくべき演算子の優先順位の組み合わせ」を紹介します。
知っておくべき演算子の優先順位
非常に多くの演算子がある中で、その優先順位を暗記することは難しく感じます。
しかし、気にする必要はありません。この優先順位を正確に丸暗記されている方はほとんどいないからです。
それは、一部のケースを除いて自然と理にかなった優先順位で計算されるため、丸暗記しなくても感覚で理解しているからです。
私もこの記事をまとめている中で「こんな優先順位なのか~」と気づかされます。
しかし、全てを覚えていないというだけで、ポイントとなる演算子については把握しています。それを解説しましょう。
覚えておくべき優先順位の関係性①:論理積と論理和
論理積(&&)と論理和(||)の演算子は、「if文」「for文」などの条件式でよく登場する演算子です。
この2つの演算子は同時に使われることもよくあるため、優先順位に気を付ける必要があります。
優先順位 | 演算子 | 名称 |
---|---|---|
12 | && | 論理積 |
13 | || | 論理和 |
この関係性から、論理積である「&&」の方が先に演算されることがわかります。
それでは問題の発生するプログラム例を示します。
subfunc関数は、2つの引数が共に「0」か「1」の時に、戻り値を「1」とする仕様だったとしましょう。
#include <stdio.h>
int subfunc(int arg1, int arg2)
{
if (arg1 == 0 || arg1 == 1 && arg2 == 0 || arg2 == 1)
{
return 1;
}
return 0;
}
int main(void)
{
printf("%d\n", subfunc(0, 0)); // ケース①
printf("%d\n", subfunc(0, 1)); // ケース②
printf("%d\n", subfunc(0, 2)); // ケース③
return 0;
}
①:1
②:1
③:1
ケース③の呼び出しでは、第2引数が「2」であるため戻り値は「0」でないといけませんが結果は「1」になっています。
このプログラムは次のように間違った順番で演算されています。
それでは()を使って正しく優先順位を調整したプログラムを示しましょう。
#include <stdio.h>
int subfunc(int arg1, int arg2)
{
if ((arg1 == 0 || arg1 == 1) && (arg2 == 0 || arg2 == 1))
{
return 1;
}
return 0;
}
int main(void)
{
printf("%d\n", subfunc(0, 0)); // ケース①
printf("%d\n", subfunc(0, 1)); // ケース②
printf("%d\n", subfunc(0, 2)); // ケース③
return 0;
}
①:1
②:1
③:0
ケース③の結果が正しく「0」と表示されましたね。
このように、論理積と論理和の組み合わせは優先順位に気を付ける必要があります。
自分が求めている演算順序になるように()を使って適切に演算させましょう。
この優先順位を理解していても、明示的に()を使ってプログラムすることもあります。
それは他者が「このプログラムって本当にあってるの?」という疑惑を持たせないためだったりします。
覚えておくべき優先順位の関係性②:AND演算子とイコール
次のように、ビット演算を行うためのAND演算子(&)、OR演算子(|)、XOR演算子(^)はイコールよりも優先順位が低いです。
優先順位 | 演算子 | 名称 |
---|---|---|
8 | == != | イコール ノットイコール |
9 | & | AND演算子 |
10 | ^ | XOR演算子(排他的論理和) |
11 | | | OR演算子 |
この中でAND演算子は、「マスク処理」と呼ばれるビット抽出処理で利用されることがあります。
このマスク処理では、イコールと併用されるため優先順位に要注意です。
次のプログラムは、変数numの最上位ビットの値を「0」か「1」で画面表示するプログラムです。
正解は「1」なのですが、間違ったマスク処理では正しく演算ができていません。マスク処理では()を使ってAND演算を先に実施する必要があるのです。
間違ったマスク処理
#include <stdio.h>
int main(void)
{
unsigned char num = 0xF0;
// マスク処理
if (num & 0x80 == 0x80)
{
printf("1");
}
else
{
printf("0");
}
return 0;
}
0
正しいマスク処理
#include <stdio.h>
int main(void)
{
unsigned char num = 0xF0;
// マスク処理
if ((num & 0x80) == 0x80)
{
printf("1");
}
else
{
printf("0");
}
return 0;
}
1
この「マスク処理」は、組み込み開発のハードウェア制御にてよく登場します。
マスク処理に関して詳しく知りたい方は『ビット演算を扱うための本当の視点と実践的な使用例を図解』を読んでおきましょう。
組み込み開発の初心者は、この不具合をよく出します。ビルドエラーが発生しないため、なかなか問題に気づきづらいのです。
ビット演算の演算子は優先順位が低いことに要注意ですよ。
覚えておくべき優先順位の関係性③:インクリメント・デクリメントと間接参照演算子
間接参照演算子(*)はポインタ制御にて出てくる演算子です。
間接参照演算子を利用する目的は、ポインタが参照しているメモリにアクセスするための記号です。
次のプログラムはmain関数で定義されたcount変数の値を、subfunc関数でインクリメントするものですが、正しく動きません。
#include <stdio.h>
void subfunc(long * pdata)
{
*pdata++;
return;
}
int main(void)
{
long count = 0;
subfunc(&count);
printf("%d", count);
return 0;
}
0
間接参照演算子とインクリメント・デクリメント(後置)は次の優先順位となっています。
優先順位 | 演算子 | 名称 |
---|---|---|
1 | ++(後置) ーー(後置) | インクリメント(後置) デクリメント(後置) |
2 | * | 間接参照演算子 |
インクリメント(後置)の方が先に実施されることがわかります。
そのため正しくプログラムを動かすためには、次のように()で間接参照演算子を先に演算する必要があります。
#include <stdio.h>
void subfunc(long * pdata)
{
(*pdata)++;
return;
}
int main(void)
{
long count = 0;
subfunc(&count);
printf("%d", count);
return 0;
}
1
count変数の値が「1」になっているのがわかります。
ポインタのアスタリスクについて理解できていない方は、『ポインタ変数定義の正しい解釈とは【「*」の意味を解説】』を見ておきましょう。
ポインタを経由してインクリメントしたいというシーンは、多くはないですがたまに出てくるシーンです。
この組み合わせも覚えておきましょう。
演算子の種類と優先順位についてのまとめ
- C言語には多数の演算子が用意されているが、徐々に使いながら覚えればよい!
- 複数の演算子が同時に使用された場合は、優先順位に従い順に演算される!
- 優先順位を全て丸暗記する必要はなく、ポイントとなる3つの組み合わせを覚えておくこと!