C言語の代入演算子は、プログラム中で変数に値を入れたり更新したりするための最も基本的な仕組みです。
単純な=だけでなく、+=や&=などの複合代入演算子を正しく使いこなすことで、コードを短く読みやすくできます。
本記事ではC言語の代入演算子の基本から、ビット演算を含む複合代入演算子、そして実践的な注意点やテクニックまで、丁寧に解説します。
C言語の代入演算子とは
代入演算子の基本
代入演算子の中心は=です。
C言語では、変数に値を入れる操作を「代入」と呼び、=を用いて記述します。
基本形は次の通りです。
左辺 = 右辺;
ここで、左辺には「代入される側(変数や配列要素など)」、右辺には「計算結果や値」がきます。

単純な代入の例
#include <stdio.h>
int main(void) {
int a; // 変数aを宣言
a = 10; // aに10を代入
int b = 3; // 宣言と同時に初期化
int c;
c = a + b; // aとbの合計をcに代入
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
return 0;
}
a = 10
b = 3
c = 13
この例では、a = 10;のように右辺が計算され、その結果が左辺の変数に格納されます。
右辺と左辺の評価順序と注意点
代入は「右辺→左辺」の順に評価されます。
つまり、右辺の式がすべて計算されてから、その結果が左辺に代入されます。

評価順序の具体例
#include <stdio.h>
int main(void) {
int x = 5;
int y;
// 右辺が先に評価される
y = x + 2 * 3; // 右辺: x + 2 * 3 → 5 + 6 → 11
printf("x = %d, y = %d\n", x, y);
return 0;
}
x = 5, y = 11
ここで重要なのは右辺が完全に計算されてから、最後に左辺へ代入が行われるという点です。
未定義動作を招きやすい書き方に注意
C言語では、同じ変数を「更新」と「参照」に同時に使い、評価順序が規定されていない書き方は危険です。
int i = 0;
int a = i++ + i++; // これは未定義動作の可能性が高く、避けるべき書き方
どちらのi++が先に評価されるかが標準で決まっていないため、コンパイラや最適化によって結果が変わる可能性があります。
このような式を代入の右辺に書くと、挙動が不定になりデバッグが困難になります。
代入と初期化の違い
「代入」と「初期化」は似ていますが、C言語では意味が異なります。
- 初期化: 変数の宣言と同時に最初の値を与えること
- 代入: すでに存在する変数の値を書き換えること

初期化と代入のコード比較
#include <stdio.h>
int main(void) {
// 初期化
int x = 10; // 宣言と同時に値10を設定
// 宣言だけ
int y; // ここでは値は不定(ローカル変数)
// 後から代入
y = 20;
printf("x = %d, y = %d\n", x, y);
return 0;
}
x = 10, y = 20
ローカル変数(関数内で宣言された変数)は、初期化しないとごみ値が入るため、宣言時に初期化するか、すぐに代入することが重要です。
また、const修飾子を使う場合は、原則として初期化が必須で、あとから代入で値を変えることはできません。
const int n = 5; // 初期化はOK
// n = 6; // エラー: const変数には代入できない
複合代入演算子(+=や-=など)の基本
複合代入演算子とは何かとメリット
複合代入演算子は「計算」と「代入」をまとめて書ける演算子です。
代表的なものは次の通りです。
+=(加算して代入)-=(減算して代入)*=(乗算して代入)/=(除算して代入)%=(剰余を代入)- ほかにビット演算向けの
&=、|=などもあります(後半で解説します)。

複合代入演算子には次のようなメリットがあります。
1. コードが短くなる 2. 左辺が複雑な式でも書き直しが少ない 3. 表現としての意図が読み取りやすくなる
+=演算子の使い方と典型例
+=は「左辺に右辺を足し込む」演算子です。
次の2つは同じ意味になります。
a = a + b;a += b;

合計値を求めるループの典型例
#include <stdio.h>
int main(void) {
int sum = 0; // 合計値を保持する変数
int i;
for (i = 1; i <= 5; i++) {
sum += i; // sum = sum + i; と同じ意味
}
printf("1から5までの合計 = %d\n", sum);
return 0;
}
1から5までの合計 = 15
「合計を蓄積していく」処理では+=が非常によく使われます。
ループ内で合計を更新するときは、sum += i;のように書くのが自然です。
-=演算子の使い方とカウンタ処理
-=は「左辺から右辺を引き算する」演算子です。
次の2つは同じ意味です。
a = a - b;a -= b;

カウントダウン処理の例
#include <stdio.h>
int main(void) {
int count = 5;
while (count > 0) {
printf("count = %d\n", count);
count -= 1; // count = count - 1; と同じ
}
printf("ループ終了時のcount = %d\n", count);
return 0;
}
count = 5
count = 4
count = 3
count = 2
count = 1
ループ終了時のcount = 0
残り回数や残高など、値が減っていく処理では-=がよく使われます。
カウンタを1ずつ増減する場合は++や--も用いられますが、任意の数だけ増減したいときは+=や-=が便利です。
*=と/=による計算の簡略化
*=と/=は、乗算・除算と代入をまとめて行います。
a *= b;はa = a * b;a /= b;はa = a / b;

*= と /= のコード例
#include <stdio.h>
int main(void) {
int x = 3;
x *= 4; // x = x * 4; → xは12になる
printf("x *= 4 の後: x = %d\n", x);
x /= 3; // x = x / 3; → xは4になる(整数同士の除算)
printf("x /= 3 の後: x = %d\n", x);
return 0;
}
x *= 4 の後: x = 12
x /= 3 の後: x = 4
ここで整数型同士の/=は小数点以下が切り捨てられることに注意が必要です。
int a = 5;
a /= 2; // aは2 (2.5ではない)
小数を扱いたい場合は、浮動小数点型(floatやdouble)を使います。
%=で余りを更新するケース
%=は、剰余(余り)を求めて代入する演算子です。
a %= b;はa = a % b;

余りを使った周期的な処理の例
#include <stdio.h>
int main(void) {
int i;
for (i = 0; i < 10; i++) {
int phase = i; // 作業用変数
phase %= 3; // phase = phase % 3;
printf("i = %d のとき phase = %d\n", i, phase);
}
return 0;
}
i = 0 のとき phase = 0
i = 1 のとき phase = 1
i = 2 のとき phase = 2
i = 3 のとき phase = 0
i = 4 のとき phase = 1
i = 5 のとき phase = 2
i = 6 のとき phase = 0
i = 7 のとき phase = 1
i = 8 のとき phase = 2
i = 9 のとき phase = 0
このように%=は周期的なパターン(0,1,2,0,1,2,…)を作るときなどに便利です。
ビット演算の複合代入
ここからはビット演算と組み合わせた複合代入を解説します。
主にフラグ管理や低レベル処理でよく使われます。
&=(ANDして代入)|=(ORして代入)^=(XORして代入)<<=(左シフトして代入)>=(右シフトして代入)

&=演算子でビットをマスクする方法
&=は「ビットANDして代入」する演算子です。
a &= b;はa = a & b;
&はビットANDなので、対応するビット同士を論理積で計算します。
マスク(mask)と呼ばれる値を使うことで、特定のビットだけを残し、他を0にすることができます。

&= を使ったマスクの例
#include <stdio.h>
int main(void) {
unsigned char value = 0b11011010; // 2進数表記(C99以降)
unsigned char mask = 0b00001111; // 下位4ビットだけを残すマスク
printf("value(元) = 0x%02X\n", value);
value &= mask; // 上位4ビットを0にする
printf("value(&= mask後) = 0x%02X\n", value);
return 0;
}
value(元) = 0xDA
value(&= mask後) = 0x0A
センサ値の下位ビットだけ取り出す、フラグの特定ビットだけ残すといった用途で頻繁に使われます。
|=演算子でフラグを立てるテクニック
|=は「ビットORして代入」する演算子です。
a |= b;はa = a | b;
|はビットORなので、いずれかのビットが1なら結果は1になります。
これを利用して、特定のビットを1に「立てる」(フラグON)ときに使います。

|= を使ったフラグ操作の例
#include <stdio.h>
#define FLAG_READ 0x01 // 0000 0001
#define FLAG_WRITE 0x02 // 0000 0010
#define FLAG_EXEC 0x04 // 0000 0100
int main(void) {
unsigned char flags = 0; // すべてのフラグがOFF
// 読み取り権限を追加
flags |= FLAG_READ;
// 書き込み権限も追加
flags |= FLAG_WRITE;
printf("flags = 0x%02X\n", flags);
return 0;
}
flags = 0x03
複数のフラグを順にONしていく処理では|=が標準的な書き方です。
^=演算子でビットを反転する使い方
^=は「ビットXORして代入」する演算子です。
a ^= b;はa = a ^ b;
XOR(排他的論理和)は、ビットが異なるときに1、同じときに0になります。
特定ビットを「トグル」(0なら1に、1なら0に)したい場合に便利です。

^= を使ったON/OFF切り替えの例
#include <stdio.h>
#define FLAG_LED 0x01 // LED用のフラグ(ビット0)
int main(void) {
unsigned char flags = 0; // すべてOFF
// LEDフラグをトグル(OFF→ON)
flags ^= FLAG_LED;
printf("1回目: flags = 0x%02X\n", flags);
// もう一度トグル(ON→OFF)
flags ^= FLAG_LED;
printf("2回目: flags = 0x%02X\n", flags);
return 0;
}
1回目: flags = 0x01
2回目: flags = 0x00
スイッチ入力に応じて状態を反転させたいときなどに、^=を使うとシンプルに記述できます。
<<=と>>=でビットシフトと効率的な演算
<<=と>=は、ビットシフトと代入を同時に行う演算子です。
a <<= n;はa = a << n;(左にnビットシフト)a >>= n;はa = a >> n;(右にnビットシフト)

ビットシフトの実例
#include <stdio.h>
int main(void) {
unsigned int x = 1;
x <<= 3; // x = x << 3; → 2の3乗(8)倍
printf("x <<= 3 の後: x = %u\n", x);
x >>= 2; // x = x >> 2; → 2の2乗(4)で割る
printf("x >>= 2 の後: x = %u\n", x);
return 0;
}
x <<= 3 の後: x = 8
x >>= 2 の後: x = 2
2のn乗倍・2のn乗分の1の計算を、乗算・除算ではなくビットシフトで行うことで高速化が期待できるケースもあります(ただし現代コンパイラは最適化を行うので、可読性を優先する場面も多いです)。
ビット演算の複合代入で注意すべき点
ビット演算の複合代入を使う際には、次の点に注意します。

1つずつ簡単に確認します。
符号付き整数での右シフト
符号付き整数(intなど)での右シフト(>=)は、算術シフトか論理シフトかが処理系依存です。
つまり、負の値を右シフトしたときの挙動が環境によって異なりうるということです。
移植性を重視する場合やビットパターンを正確に扱いたい場合は、符号なし整数(unsigned intなど)を使用します。
シフト量の上限
ビット数以上のシフトは未定義動作です。
unsigned int x = 1;
x <<= 32; // 32ビット環境で「32ビット左シフト」は未定義動作の可能性
シフト量が0〜(ビット数−1)の範囲に収まっているかを必ず確認する必要があります。
意図しないビット操作
&=、|=、^=を使うときは、マスク値を間違えると意図しないビットまで変更してしまうことがあります。
マクロ定義や列挙型などを利用し、マスクをわかりやすく管理するのが安全です。
C言語の代入演算子の注意点と実践テクニック
代入演算子と比較演算子
C言語では=と==を厳密に区別する必要があります。
=: 代入演算子(値を書き換える)==: 比較演算子(等しいかどうかを判定する)

間違えやすい例
#include <stdio.h>
int main(void) {
int x = 5;
if (x = 0) { // x == 0 と書きたいところで誤って x = 0 と書いている
printf("xは0です\n");
} else {
printf("xは0ではありません\n");
}
printf("xの値 = %d\n", x);
return 0;
}
xは0ではありません
xの値 = 0
このプログラムは、if (x = 0)でxに0を代入し、その値(0)が条件として評価されます。
0は「偽(false)」なのでelse側が実行されます。
さらに、xの値は0に書き換わってしまっています。
比較には必ず==を使うことを習慣づけてください。
連続代入(a = b = c)の仕組みと落とし穴
C言語では、代入式は値を返すため、次のような連続代入が可能です。
a = b = c = 10;

連続代入の動作確認
#include <stdio.h>
int main(void) {
int a, b, c;
a = b = c = 10;
printf("a = %d, b = %d, c = %d\n", a, b, c);
return 0;
}
a = 10, b = 10, c = 10
このように、代入は右から左に結合するため、まずc = 10が評価され、その値10がb = 10、さらにa = 10へと連鎖していきます。
落とし穴: 読みづらさとバグの元
連続代入は便利な反面、複雑な式を1行に詰め込むと読みづらくなり、バグを招きます。
// 一見するとわかりづらいコード
a = b = func() + (c = 5);
値の代入と式の評価を分けて書くほうが、安全で読みやすいことが多いです。
代入式の戻り値とif文での利用
先ほど触れたように、代入式そのものが値(代入後の値)を返します。
この性質を活かして、if文などで直接使うことができます。

典型的な利用例(getchar)
#include <stdio.h>
int main(void) {
int ch;
printf("何か文字を入力してEnterを押してください(EOFで終了)\n");
// (ch = getchar()) が代入と同時に値を返す
while ((ch = getchar()) != EOF) {
if (ch == '\n') {
continue; // 改行はスキップ
}
printf("入力された文字: %c (コード: %d)\n", ch, ch);
}
return 0;
}
何か文字を入力してEnterを押してください(EOFで終了)
(ここでユーザが入力する。動作例は省略)
このように入力関数の結果を代入して、そのまま比較に使う書き方はC言語でよく見られます。
ただし、さきほどの「=と==の誤記」と区別がつきにくい場面もあるため、括弧で囲って意図を明確にすることが推奨されます。
型変換と代入
代入を行うとき、右辺の値は左辺の型に変換されます。
これを「代入時の暗黙の型変換」と呼びます。

小数から整数への代入
#include <stdio.h>
int main(void) {
double d = 3.7;
int i;
i = d; // 小数部分が切り捨てられる
printf("d = %f, i = %d\n", d, i);
return 0;
}
d = 3.700000, i = 3
浮動小数点から整数への代入では、小数部分が切り捨てられることに注意してください。
範囲外の値の代入
#include <stdio.h>
int main(void) {
int big = 1000;
char c;
c = big; // charの範囲外になる可能性がある
printf("big = %d, c = %d\n", big, c);
return 0;
}
big = 1000, c = (環境依存の値)
変数の型の表現範囲を超える値を代入すると、結果は処理系依存(未定義動作になる場合も)です。
意図しないオーバーフローや桁落ちが起こっていないか、代入前に確認する必要があります。
必要に応じて明示的なキャスト((int)や(double))を記述し、意図した変換であることを示すと読みやすくなります。
読みやすい代入演算子の書き方とコーディング規約
代入演算子は頻出するため、読みやすさやバグの防止を意識した書き方が重要です。
代表的なポイントを挙げます。

スペースの入れ方
多くのコーディング規約では、演算子の前後にスペースを入れることが推奨されています。
// 読みやすい
x = y + 1;
sum += value;
// 詰め込みすぎて読みにくい例
x=y+1;
sum+=value;
代入と比較の混在を避ける
条件式の中で代入と比較を混在させると、バグの温床になります。
// あまり推奨されない
if ((x = func()) == 0) {
...
}
// 安全寄りの書き方の例
x = func();
if (x == 0) {
...
}
特に学習段階では、代入は代入、条件判定は条件判定と、行を分けて書くほうがミスを減らせます。
複合代入は意味が明確なときに使う
+=や&=などの複合代入は便利ですが、「元の値に足し込む」「このビットをONにする」などの意図が明確な場合に使うと、コードの目的が読み取りやすくなります。
逆に、複雑な式の中でさらに複合代入を行うと、何をしているのか一目でわかりにくくなります。
読みやすさを優先し、場合によっては通常の代入に分ける判断も重要です。
まとめ
C言語の代入演算子は単なる=だけでなく、+=や&=などの複合代入を含めて理解することで、コードの表現力と読みやすさが大きく向上します。
右辺→左辺の評価順序や、初期化との違い、ビット演算におけるマスクやフラグ操作、さらに代入式が値を返す性質と型変換の影響まで押さえることが重要です。
実際の開発では、バグを生みにくい書き方・コーディング規約も意識しながら、目的に応じて代入演算子を使い分けていくようにしてください。
