C言語の演算子を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
演算子とは
演算子とは,データとデータを結びつけて何らかの演算をするための記号です.
演算子の存在はC言語に限ったことではなく,プログラミング言語であれば必ずあります.
演算子がないとプログラミングができませんからね...
C言語には,特に多くの演算子があります.
C言語の演算子の一覧は以下になりますので,それぞれ解説していきます.
- 算術演算子
- 等値演算子と関係演算子
- 論理演算子
- インクリメント演算子とデクリメント演算子
- ビット演算子とシフト演算子
- 代入演算子
- 3項演算子(条件演算子)
- カンマ演算子
- キャスト演算子
- sizeof演算子
- ポインタ演算子
算術演算子
算術演算子は,多くのプログラミング言語に存在する演算子です.
それだけに多くの言語で似たような記号になっています.
下表に示すように,C言語では四則演算(足し算,引き算,掛け算,割り算)と剰余(余り),正符号と負符号の7個の算術演算子が定義されています.(足し算と正符号は両方とも+を利用します.)
記号 | 説明 | 式の例 |
---|---|---|
+ | 足し算 | a = b + c |
- | 引き算 | a = b - c |
* | 掛け算 | a = b * c |
/ | 割り算 | a = b / c |
% | 剰余(余り) | a = b % c |
+ | 正符号 | a = +b |
- | 負符号 | a = -b |
剰余は,剰余演算子(%)の符号の注意点で詳しく解説しているので,興味があるあなたはこちらも読みましょう!
PythonやRubyにある「べき乗演算子(**)」はありませんので注意して下さい.
C言語のべき乗の方法を知りたいあなたは,pow関数と自作関数でべき乗,累乗,2乗の計算を読みましょう.
算術演算は,他の言語と同様に特に難しいことはありません.
ただし,C言語には変数の型というものがあります.
算術演算時に異なる型を混在させると規則に従った暗黙的な型変換が行われます.
詳細を知りたいあなたは,キャスト演算子で明示的な型変換【暗黙的な型変換も紹介】を読みましょう.
等値演算子と関係演算子
等値演算子(==,!=)は式と式の等値関係を評価し,関係演算子(<,<=,>,>=)は大小関係を評価するために利用されます
これらの演算子は優先順位が異なるため,別々の名前が付いています.
具体的には,関係演算子の方が等値演算子よりも優先順位が高くなっています.
等値演算子は下表になります.
演算子 | 意味 |
---|---|
== | 左辺と右辺が等しい時に真 |
!= | 左辺と右辺が等しくない時に真 |
関係演算子は下表になります.
演算子 | 意味 |
---|---|
< | 左辺の方が右辺より小さい時に真 |
<= | 左辺が右辺以下の時に真 |
> | 左辺の方が右辺より大きい時に真 |
>= | 左辺が右辺以上の時に真 |
また,C言語の真偽値は,下表のように0であるかないかという整数値で決まります.
したがって,等値演算子や関係演算子の演算においても,偽ならばその式の値が0になり,真ならば0以外の値になります.
真偽 | 値(整数型) |
---|---|
真 | 0以外の値 |
偽 | 0 |
ここで注意してほしいのは,等しいかどうかを比較するときには==という記号を利用するということです.
=という記号に間違えてしまう傾向にあります.
==の部分を=にしても文法的には間違いではなく,ただの代入文になります.
なので,コンパイル時にエラーにならないので注意して下さい.
GCC/Clangでは,if文等の条件式で==を間違えて=と書いてコンパイルした時に,以下の警告メッセージを表示します.
- GCC:warning: suggest parentheses around assignment used as truth value [-Wparentheses]
- Clang:warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
日本語訳は「条件式ではカッコを付けましょう」という意味ですが,==を間違えて=と書いてしまった時に表示されるメッセージです.
デバッグに有用なので覚えておきましょう.
整数を等値演算子と関係演算子で比較するコード
整数を等値演算子と関係演算子で比較するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { int a = 10; printf("(a = 10) %d\n", (a = 10)); printf("(a == 10) %d\n", (a == 10)); printf("(a != 10) %d\n", (a != 10)); printf("(a < 10) %d\n", (a < 10)); printf("(a <= 10) %d\n", (a <= 10)); printf("(a > 10) %d\n", (a > 10)); printf("(a >= 10) %d\n", (a >= 10)); return 0; } |
実行結果は以下になります.
1 2 3 4 5 6 7 8 9 |
$ gcc equal_and_relational_operators.c $ a.out (a = 10) 10 (a == 10) 1 (a != 10) 0 (a < 10) 0 (a <= 10) 1 (a > 10) 0 (a >= 10) 1 |
浮動小数点数を等値演算子と関係演算子で比較するコード
浮動小数点数を等値演算子と関係演算子で比較するコードは,計算誤差(丸め誤差,打ち切り誤差,情報落ち,桁落ち)が発生するため正常に動作しないことがありますので注意して下さい.
※41~42行目のDBL_EPSILONは1と1より大きい最小の数との差です.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <float.h> #include <math.h> #define PRECISION 50 int main(void) { double d = 0.0; double e, f, g; printf("(d = 0.0) %lf\n", (d = 0.0)); printf("(d == 0.0) %d\n", (d == 0.0)); printf("(d != 0.0) %d\n", (d != 0.0)); printf("(d < 0.0) %d\n", (d < 0.0)); printf("(d <= 0.0) %d\n", (d <= 0.0)); printf("(d > 0.0) %d\n", (d > 0.0)); printf("(d >= 0.0) %d\n", (d >= 0.0)); d = e = 3.14; printf(" d = %.*lf\n", PRECISION, d); printf(" e = %.*lf\n", PRECISION, e); printf(" d - e = %.*lf\n", PRECISION, d - e); printf("(d == e) %d\n", (d == e)); printf("(d != e) %d\n", (d != e)); f = 31.4 / 10; printf(" d = %.*lf\n", PRECISION, d); printf(" f = %.*lf\n", PRECISION, f); printf(" d - f = %.*lf\n", PRECISION, d - f); printf("(d == f) %d\n", (d == f)); printf("(d != f) %d\n", (d != f)); g = fabs(d - f); printf(" g = %.*lf\n", PRECISION, g); printf(" (g == %.*lf) %d\n", PRECISION, 0.0, g == 0.0); printf(" (g <= %.*lf) %d\n", PRECISION, DBL_EPSILON, g <= DBL_EPSILON); printf(" (g <= %.*lf) %d\n", PRECISION, 10 * DBL_EPSILON, g <= 10 * DBL_EPSILON); return 0; } |
実行結果は以下になります.
以下の10~11行目,15~17行目,20~23行目でd,e,f,gを利用した計算結果を小数第50位まで表示しています.
equal_and_relational_operators2.cの24行目で,dとeに3.14を代入したため,以下の13~14行目でdとeは正常に比較できています((「d == e) 1」と「(d != e) 0」).
これに対して,equal_and_relational_operators2.cの31行目で,fに31.4 / 10を代入しています.
数学的には計算結果は3.14ですが,情報誤差が発生したため以下の18~19行目で正常に比較できていないことに注意して下さい(「(d == f) 0」と「(d != f) 1」).
equal_and_relational_operators2.cの38行目で,gにfabs関数でd -fの絶対値|d-f|の計算結果を代入しています.
以下の20~23行目で,計算誤差の大きさに依存して同じ(誤差が特定の小さい値以下)と判定できる場合が変化します.
gの計算誤差は2 * DBL_EPSILONなので,21~22行目の比較結果は偽ですが,23行目の比較結果は真になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$ gcc equal_and_relational_operators2.c $ a.out (d = 0.0) 0.000000 (d == 0.0) 1 (d != 0.0) 0 (d < 0.0) 0 (d <= 0.0) 1 (d > 0.0) 0 (d >= 0.0) 1 d = 3.14000000000000012434497875801753252744674682617188 e = 3.14000000000000012434497875801753252744674682617188 d - e = 0.00000000000000000000000000000000000000000000000000 (d == e) 1 (d != e) 0 d = 3.14000000000000012434497875801753252744674682617188 f = 3.13999999999999968025576890795491635799407958984375 d - f = 0.00000000000000044408920985006261616945266723632812 (d == f) 0 (d != f) 1 g = 0.00000000000000044408920985006261616945266723632812 (g == 0.00000000000000000000000000000000000000000000000000) 0 (g <= 0.00000000000000022204460492503130808472633361816406) 0 (g <= 0.00000000000000222044604925031308084726333618164062) 1 |
論理演算子
論理演算子は,主に関係演算子等を利用した式を複数組み合わせる時に利用します.
論理演算子を下表に示します.
記号 | 説明 |
---|---|
! | 論理否定 |
&& | 論理積 |
|| | 論理和 |
論理演算子を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { char c = 'c'; printf("(c == 'c'): %d\n", (c == 'c')); printf("!(c == 'c'): %d\n", !(c == 'c')); printf("c is between \'a\' and \'z\'.: %d\n", (c >= 'a' && c <= 'z')); printf("c is not lower than \'a\' or greater than \'z\'.: %d\n", !(c < 'a' || c > 'z')); return 0; } |
実行結果は以下になります.
1 2 3 4 5 6 |
$ gcc logical_operators.c $ a.out (c == 'c'): 1 !(c == 'c'): 0 c is between 'a' and 'z'.: 1 c is not lower than 'a' or greater than 'z'.: 1 |
インクリメント演算子とデクリメント演算子
インクリメント演算子は値を1増やす,デクリメント演算子は値を1減らす演算子です.
ここで,インクリメントは増加する,デクリメントは減少するという意味です.
以下のように,for文等で値を1増やす,または1減らすという処理を書きたい時がありますよね.
1 |
i = i + 1; |
C言語ではこのような操作を簡単に記述するために,インクリメント演算子とデクリメント演算子という専用の演算子を導入しています.
インクリメント演算子とデクリメント演算子は下表になります.
記号 | 意味 | 式の例 |
---|---|---|
++ | 1を増やす | ++a a++ |
-- | 1を減らす | --a a-- |
まず,これらの演算子の使い方を説明します.
以下の3つの文は同じ意味になります.
1 2 3 |
a = a + 1; ++a; a++; |
上記の++aを前置インクリメント(pre-increment),a++を後置インクリメント(post-increment)と呼びます.
同様に,以下の3つの文は同じ意味になります.
1 2 3 |
a = a - 1; --a; a--; |
上記の--aを前置インクリメント(pre-decrement),a--を後置インクリメント(post-decrement)と呼びます.
式の値自体を参照しない単純な計算では,前置型と後置型のいずれを利用しても同じ結果になります.
しかし,以下のように式の値を参照する場合では両者の意味が異なりますので注意して下さい.
1 2 3 4 |
b = ++a; // 代入前に1増やす b = a++; // 代入後に1増やす b = --a; // 代入前に1減らす b = a--; // 代入後に1減らす |
上記の違いを以下のコードで示します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { int a, b; a = 0; b = 0; printf("a = %d, b = %d\n", a, b); b = ++a; /* pre-increment. */ printf("a = %d, b = %d\n", a, b); a = 0; b = 0; printf("a = %d, b = %d\n", a, b); b = a++; /* post-increment. */ printf("a = %d, b = %d\n", a, b); a = 0; b = 0; printf("a = %d, b = %d\n", a, b); b = --a; /* pre-decrement. */ printf("a = %d, b = %d\n", a, b); a = 0; b = 0; printf("a = %d, b = %d\n", a, b); b = a--; /* post-decrement. */ printf("a = %d, b = %d\n", a, b); return 0; } |
実行結果は以下になります.
1 2 3 4 5 6 7 8 9 10 |
$ gcc increment_and_decrement_operators.c $ a.out a = 0, b = 0 a = 1, b = 1 a = 0, b = 0 a = 1, b = 0 a = 0, b = 0 a = -1, b = -1 a = 0, b = 0 a = -1, b = 0 |
これらの代入文は,一般的には以下のように記述できます.
インクリメント,デクリメント | 一般的な記述 |
---|---|
b = ++a; | a = a + 1; b = a; |
b = a++; | b = a; a = a + 1; |
b = --a; | a = a - 1; b = a; |
b = a--; | b = a; a = a - 1; |
一般的な記述をすると上記のように2つの文になってしまいます.
そこで,インクリメント演算子とデクリメント演算子を利用することで,a[i++]やb[--j]等のように式しか記述できない部分に記述できます.
ビット演算子とシフト演算子
ビット演算子とシフト演算子は,こちらの記事で深掘りしています.
代入演算子
代入演算子は,変数に(演算結果を含む)値を代入するために利用される演算子です.
実際のコードでは,以下のように自分自身に何かの演算をするという記述がよく出てきます.
この例では,1つの式の中で同じ変数が2度出てきます.
1 |
i = i + 1; |
また,変数名が長いと以下のようになります.
1 |
current_thread[current_cpu] = current_thread[current_cpu] + 0x10; |
こうするとキー入力も大変ですし,間違える(タイポする)可能性が高くなります.
そこで,C言語では簡単に記述できる代入演算子が用意されています.
上記の文は,以下のように書くことができます.
1 |
current_thread[current_cpu] += 0x10; |
これならタイプ数が減り,間違える可能性が低くなります.これが代入演算子のメリットです.
代入演算子の一覧を下表に示します.もちろん,たたの=も代入演算子の一つです.
記号 | 式の例 | 一般記述法 |
---|---|---|
= | a = b | a = b |
+= | a += b | a = a + b |
-= | a -= b | a = a – b |
*= | a *= b | a = a * b |
/= | a /= b | a = a / b |
%= | a %= b | a = a % b |
&= | a &= b | a = a & b |
|= | a |= b | a = a | b |
^= | a ^= b | a = a ^ b |
<<= | a <<= b | a = a << b |
>>= | a >>= b | a = a >> b |
このように,代入演算子は演算と代入を1度にできる便利な演算子ですが,注意点があります.
例えば「+=」という演算子は,「+ =」と余分なスペースを入れてはいけません.
これは代入演算子だけでなく,>=,<=,==,!=,&&,||,++,--,<<,>>等の演算子も余分なスペースを入れてはいけません.
また,以下の2つの文は同じ意味になります.
1 2 |
a /= b – 10; a = a / (b – 10); |
「a = a / b – 10;」とはならないので,注意して下さい.
つまり,以下の2つの文は同じ意味になります.
1 2 |
a /= b – 10; a /= (b - 10); |
3項演算子(条件演算子)
3項演算子(条件演算子)はif文のような使い方をします.
例えば,以下のように利用されます.
1 |
a = (x > y) ? x : y; |
3項演算子は,次のように3つの項をとります.
まず式1が評価され,それが真ならば式2,偽ならば式3がこの式全体の値になります.
これが,3項演算子と呼ばれる理由です.
1 |
式1 ? 式2 : 式3 |
先の例ではxがyより大きい時はxが式の値となり,そうでないときにはyが式の値になり,aに代入されます.
3項演算子を利用したコード例は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { int a, x, y; x = 5; y = 8; a = (x > y) ? x : y; printf("x = %d, y = %d, a = %d\n", x, y, a); x = 3; y = -2; a = (x > y) ? x : y; printf("x = %d, y = %d, a = %d\n", x, y, a); (x > y) ? printf("x > y.\n") : printf("x <= y.\n"); return 0; } |
実行結果は以下になります.
1 2 3 4 5 |
$ gcc conditional_operators.c $ a.out x = 5, y = 8, a = 8 x = 3, y = -2, a = 3 x > y. |
3項演算子は,式しか記述できない部分で比較したい場合に効果的です.
例えば,配列の添字でa[(x > y) ? x : y]のような使い方も可能です.
カンマ演算子
カンマ演算子を利用すると,本来1つしか式を記述できない部分に複数の式を記述することができます.
例えば,以下の文があったとします.
1 2 |
i = 0; j = 1; |
上記の2つの文は,カンマ演算子を利用することで以下の1つの文で記述できます.
1 |
i = 0, j = 1; |
カンマ演算子は,左から右に実行され,評価されます.
そして最後に評価(実行)された式が全体の式の値になります.
例えば,以下の文では,最初にaに1が代入され,次にbに2が代入されます.
そして,カッコの式の値は2になり,その式の値(2)がxに代入されます.
1 |
x = (a = 1, b = 2); |
カンマ演算子の説明をするために,以下のようなコードで考えてみましょう.
1 2 3 4 5 6 |
sum = 0; mul = 1; for (i = 1; i <= 10; i++) { sum = sum + i; mul = mul * i; } |
このコードでは,for文の実行に先立って,変数sumを0にmulを1に初期化しています.
カンマ演算子を利用すれば,この初期化の文をfor文の中に取り込んで,コンパクトに記述できます.(代入演算子も利用しています.)
1 2 3 4 |
for (sum = 0, mul = 1, i = 1; i <= 10; i++) { sum += i; mul *= i; } |
また,以下の例では,while文の条件式にカンマ演算子を利用して2つの式を記述しています.
まず,scanf関数でiに値を入力します.
次に,そのiが10未満の場合にwhile文の条件式は真になり,while文の中身を実行します.
iが10以上の場合はwhile文条件式が偽になるので,while文の中身を実行せずに次の処理に進みます.
1 |
while (scanf("%d", &i), i < 10) { |
キャスト演算子
キャスト演算子を知りたいあなたは,キャスト演算子で明示的な型変換【暗黙的な型変換も紹介】を読みましょう.
sizeof演算子
sizeof演算子を知りたいあなたは,sizeof演算子の使い方を読みましょう.
ポインタ演算子
ポインタ演算子を知りたいあなたは,ポインタとはを読みましょう.
まとめ
C言語の演算子を紹介しました.
C言語には多くの演算子がありますので,正しく理解してシンプルで読みやすいコードを書けるように使いこなしましょう.
演算子の優先順位と結合規則を知りたいあなたは,こちらの記事を読みましょう.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!