今回は、基本以外の演算子についてまとめて解説していますので、少し内容が長いです。一気に読み切るのでは無く、必要に応じて VS2022 で実際の動作を確認しながら理解を進めてください。
--
いきなりここに飛んで来ちゃった人は、よろしければ下記からご覧ください。
C言語基礎講座インデックス


  • ±1 専用命令
実は C言語はアセンブラに近いためか、+1 や -1 に関しては専用の記述方法が用意されています。まずはどう書くのか見てみましょう。

int n1 = 10; n1++;
これで n1 は +1 されて 11 が格納されています。n1 = n1 + 1; と同じですが、見ての通り書き方としてはすごくシンプルになります。これをインクリメントと呼びます。-1 は(想像が付くかもですが)以下のように記述します。

int n2 = 5; n2--;
これで n2 は 4 になります。-1 の場合はデクリメントと呼びます。かなり独特な書き方ですが、C言語でプログラムを記述していくと、この書き方は大変多くの場面で見かけます。さらに、

++n1;
こんな書き方も出来ます。前に配置するので前置(ぜんち)インクリメントと呼びます。先の n1++ は後置(こうち)インクリメントと呼びます。個人には前置の書き方が好きです。理由は昔のコンパイラだと、n1++; と書くより、++n1; と書く方が明らかに高速なバイナリを出力したためです。今は最適化が進んだため、実行速度の差は感じなくなりましたので、どちらでも良いと思います。
※ 2002年当時調べ

…が、実はこれ、記述場所によってはどちらでもいいわけではない落とし穴があります。それが計算順序です。計算した結果を続けて代入できるという事は、こんな書き方も出来るのです。

int n1 = 11; int n2 = 3; int ans = ++n1 / n2;
この結果の ans の値を予想してください。PR 挟みますw※ なんと電流量を抑止する事で明るさやハンダごての温度を調節するスイッチ値です。ある意味大変漢らしい…(汗

どうですか、予想できましたか。この場合は、n1 が ++ でインクリメントされて 12 になり、その結果が n2 の値である 3 で割られて ans に代入されて 4 になります。続けて確認です。

int n1 = 11; int n2 = 3; int ans = n1++ / n2;
この結果は、鋭い人なら気がついたかもですが、11 がインクリメントされる前に n2 で割られてしまうので、11÷3 の計算結果である 3.666666 が暗黙の型変換が入って 3 になるのです。そして、その後で、n1 が +1 されて 12 になるという動作をします。

このインクリメントとデクリメントの複合計算は、C言語では大変多く用いられます。まずは最もシンプルなこの状態で、動作の理解が出来るようにしておいてください。VS2022 では、下記のプログラムを入れてステップトレースすることで、変数の変化が見て取れると思います。

#include <stdio.h> int main() { int n1, n2, ans; n1 = 11; n2 = 3; ans = ++n1 / n2; printf("前置インクリメントの結果は%dです。\n", ans); n1 = 11; n2 = 3; ans = n1++ / n2; printf("後置インクリメントの結果は%dです。\n", ans); }
インクリメント動作結果
printf で補足です。文字列の中に "¥n" と書くと改行します。もし、¥n を付けないと、ずらずらと後ろに繋がって表示されます。これはエスケープシーケンスと呼ばれています。書式文字列だとか、エスケープシーケンスだとか、C言語では文字列に意味を持たせていて、それも理解を阻む原因になっています。全部覚える必要は無いので、使う分だけ覚えていけば良いと思います。あるいは使うたびに調べるでも良いかと思います。


  • シフト演算子
コンピュータでは bit を意識する演算もいくつか用意されています。そのため、意識して使わない限りは、全く使用しなくてもプログラムは作れるのですが、C言語はよりアセンブラに近いコンパイラですので、とりあえず、シフト演算だけは先に説明しておきます。

シフト演算を語る上でどうしても理解しておかねばならないのが2進数です。2進数の 0 と 1 の事を bit(ビット)と言っているわけですが、その bit の位置を左右にズラす事をシフトと呼んでいるのです。右にズラすと右シフト、左にズラすと左シフトです(当たり前)。

左シフトは << という演算子を使います。右シフトは >> です。なんとなく矢印記号の ← → に見えてきませんか。これがシフト演算子です。そんなイメージで覚えておいてください。記述方法は以下の通りです。

char bittest = 0xD9; bitL = bittest << 1; /* 左に1回シフトする */ bitR = bittest >> 2; /* 右に2回シフトする */
シフト演算子の右側の数字はシフト回数です。この数値指定には定数以外に変数も使用する事が出来ます。ただ、あまり大きい数字は使わないですね。
※ 回数に実数は使えません。

動作についてより細かく見ていきます。例えば 0xD9 という数値があるとします。この 0xD9 は 2進数に直すと 0b11011001 という表記になります。これを左シフトすると何が起きるのか。char 型で説明してみます。
右から0が入って左側が押し出される
このように右側から 0 が入ってきます。そして、左に押し出されます。左にずれた値になる

char 型は1バイト(8bit)のサイズしか保存できませんので、左側の bit が押し出されて消え失せます。結果、0b10110010 となります。16進数では 0xB2 に変わります。これがもし char 型ではなく int 型だと、VS2022 では 32bit を扱えるので、押し出された bit は変数内に残ります。そのため、int 型であれば 0xD9 を左シフトしたら 0x1B2 という数値になります。

右シフトも考え方は同じです。左から bit が入ってきて右に押し出されます。ただ、ちょっとだけ違うのは、左から入ってくる bit は 0 とは限らない点です。この動作は型が符号付きか、符号無し(unsigned 修飾子付き宣言)かと異なります。

先に2進数の中の特定の bit の呼び方を説明します。2進数では一番左側の bit を最上位ビット(MSB)と呼んでいます。逆に一番右側の bit を最下位ビット(LSB)と呼んでいます。

右シフトでは、符号付きの場合は MSB と同じ bit の値が入ってきます。符号なしの場合は必ず 0 が入ってきます。符号付きは値がマイナスの際は MSB が必ず 1 になっているため、このような動作になります。符号付きのメモリ内の状態は2の補数という状態で格納されています。これの説明は長くなるのと、直接的に C言語の説明とも異なるので、興味があればこちらをご覧ください。
2の補数 - Wikipedia

このシフトという動作はとても面白い性格を持っています。それは、左にシフトすると値は 2倍になるのです。そして、右シフトすると値は半分になるのです。まずは、数字の 5 を元に char 型でどんどん左シフトして確認してみます。2進数、10進数符号無し(unsigned char)、10進数符号あり(char)で確認してみます。※ この環境では char は符号付きと見なしています。

0b00000101 55
0b00001010 1010
0b00010100 2020
0b00101000 4040
0b01010000 8080
0b10100000 160-96
0b01000000 6464
0b10000000 128-128
0b00000000 00

如何でしょうか。80 まではちゃんと2倍になっていますよね。そして、本来 160 となる時点で符号付き側はいきなり -96 と値が小さくなっています。これは、char 型が扱える数値の限度を超えたためです。符号無し char は 0 ~ 255、符号ありの場合は -128 ~ 127 が扱える範囲で、そこから逸脱した数値は正しく表現できないのです。

左にシフトした、つまり、数値を大きくした結果、扱える範囲を逸脱する事をオーバーフローと呼んでいます。整数の右シフトでは発生しませんが、四則演算の結果、扱える値が範囲を下回った場合をアンダーフローと呼んでいます。

このようなオーバーフローやアンダーフローのようなエラーは、現在のコンピュータで int で数値を扱っていると、初級の頃はあまり目にする事はないですが、実務レベルのプログラム演算では意識しないとハマる元になります。なので、char を文字以外に使う事はまずありません。特に理由がない限りは使わないのが無難です。
【純正品】DualSense ワイヤレスコントローラー ミッドナイト ブラック (CFI-ZCT1J01)
ソニー・インタラクティブエンタテインメント
※ 私が思うに、コントローラは結局は純正品が一番良いと思うんです。PS5 のコントローラは普通に買えるんですね。


  • 四則演算の優先順位
四則演算は関数電卓と同様に優先順位があります。安物の電卓では入力した順番ですが、C言語ではそうではなく、乗除算が先に計算されてから加減算という順番となります。例えば、

int num = 1 + 2 * 3;
この計算は、2 * 3 が先に計算されてから、その結果と + 1 計算されますので、7 という値になります。9 ではない点にご注意ください。優先順位が同じ場合は、右側から計算されていきます。例えば、こんな書き方が出来るのですが、

int n1, n2; n1 = n2 = 1 * 2 + 3 * 4;
最初に 3 * 4 が計算されて 12 になります。続いて、1 * 2 が計算されて 2、それが加算されるので、14 という結果が出来ます。それがまず n2 に格納されて、続いて n2 の内容が n1 に格納されます。

これはまだ簡単なので、そうそう勘違いも無いかと思いますが、複雑な式になるとわかりにくい事があります。慣れないうちはこの演算の優先順位でバグを生む事もあるかと思います。シフト演算子が絡むとさらにわかりにくくなります。

その場合は、明示的に括弧を記述する事で、計算の優先度を明確にしたり計算優先度を変更できます。先の例ですが、括弧を…

int n1, n2; n1 = n2 = 1 * (2 + 3) * 4;
このように付けると、2 + 3 が最優先で計算されて 5 が出来ます。その次に 4 * 5 * 1 と乗算されていって、20 という結果が変数に代入されます。


  • 複合代入演算子
= は、右側から左側に代入する動作を行います。そのため、この = をC言語の用語では代入演算子と呼んでいます。そして、C言語では代入時に同時に四則演算を行う事も出来ます。加算代入とか減算代入とか言われますが、それらをまとめて複合代入演算子と言っています。…はい、これも名称は覚える必要は無いです。こういう書き方をしたら、こういう結果になるんだと言う事を覚えましょう。

この複合代入の書き方も、C言語では頻繁にお目にかかる代表的な記述方法なのです。では、まず例から。

int n1 = 12; int n2 = 3; n1 = n1 / n2;
このプログラムの動作はもう分かりますよね。n1 の 12 が、n2 の 3 で割られて、4
という結果になり、それがまた n1 に代入されます。このような、計算した結果をまた元の変数に入れるという動作を、簡略して記述できるのが複合代入演算子なのです。この書き方に変えてみます。

int n1 = 12; int n2 = 3; n1 /= n2;
はい、とてつもなく見た事もないような書き方になりました(汗)。これは n1 に対して /(除算)を n2 と行って、その結果をまた n1 に入れるという動作をします。これはまだ分かりやすい方でしょうか。では、四則演算が混ざるとどうなるでしょうか。例えばこうです。

int n1 = 12; int n2 = 2; n1 /= n2 + 1;

動作は以下のどちらになると思いますか?
  1. 12 ÷ 2 が実行されて 6 になってから +1 されるから 7 となる。
  2. 右辺の n2 + 1 が先に実行された 3 で 12 を割るので 4 となる。

この結果の予想をしてから、広告の下を見てください。※ そういや来月発売です(2022年8月現在)。もう予約は済んでます?

正解は 4 です。複合代入演算子は右辺の結果を左辺と計算する動作なんです。
C006 複合代入演算子
複合代入演算子はほぼ全ての計算式に適用できます。単純な種類だけで言えば、ここまで説明してきただけでも +=, -=, *=, /=, %=, <<=, >>= が使えます。またインクリメントやデクリメントが混ざるとどうなるかは VS2022 でステップトレースで確認してみてください。例えばこんなプログラムで確認してみましょう。

#include <stdio.h> int main() { int n1, n2; n1 = 12; n2 = 2; n1 /= n2++; printf("後置インクリメントを含めた複合代入演算の結果は%dです。\n", n1); n1 = 12; n2 = 2; n1 /= ++n2; printf("前置インクリメントを含めた複合代入演算の結果は%dです。\n", n1); }
複合代入演算子動作結果
いや、ホントにインクリメントとデクリメントが混ざった動作は理解しづらいですね。でも、そのうち感覚的に理解できるようになります。大丈夫、あなたなら出来ます!


  • (オマケ)ゼロ除算エラー
意外と多い実行時エラーがこのゼロ除算です。このエラーは、数値をゼロで割ると発生します。実際の数学でゼロで割ると結果は無限大になります。C言語で扱える型には、有効範囲が必ずあります。その有効範囲に収まらないため、ゼロ除算エラーとなります。パソコンでは実行強制停止となりますが、ゲーム機だとハングアップとなります。どちらにせよ致命的な状態です。

割る数にゼロが来る可能性がある場合は、必ずゼロかどうかを判断する処理が必要です。あるいはエラー処理を入れるか…ですが、これはどちらも先の話になってしまいます。今はゼロで割らないように気をつけるようにしてください。


  • まとめ
  1. ±1 専用のインクリメントとデクリメントという演算子がある。
  2. インクリメントとデクリメントは記述する位置で動作が変わる。
  3. シフト演算子は bit の位置をズラす。
  4. 型の有効範囲からのはみ出しに注意する。
  5. 四則演算の優先順位が分かりにくい時は()で括る。
  6. 複合代入演算子も演算優先順位に注意する。
  7. ゼロ除算エラーに注意する。

>> C言語007 数値や文字を出力するに進む
>> C言語基礎講座インデックスに戻る

良かったらポチって頂けるとやる気が上がります!ご協力感謝です!!※ もしもコロナにかかって外出できなくなったらと考えた時、そういう非常食を買い揃えておく事は重要だと思い知りました。