C言語で値を1だけ増減させたいときに使うのが++
と--
です。
短く書けて読みやすさも上がる一方で、書き方を誤ると動作未定義になる危険もあります。
本記事では「どんな場面で、どう安全に使うか」に焦点を当て、初心者の方でも迷わないルールとNG例、実用的な使いどころを丁寧に解説します。
++と–の基本
インクリメント(++)とは
インクリメント(++)
は、変数の値を1だけ増やす演算子です。
書式は変数++
または++変数
の2通りがありますが、本記事では「1増える」という効果のみ扱い、前置(先頭に付ける)と後置(末尾に付ける)の違いは別記事で解説します。
例: 1だけ増やす
#include <stdio.h>
int main(void) {
int x = 10;
printf("初期値 x = %d\n", x);
x++; // x を 1 増やす(11になる)
printf("x を 1 増やした後: %d\n", x);
return 0;
}
初期値 x = 10
x を 1 増やした後: 11
デクリメント(–)とは
デクリメント(--)
は、変数の値を1だけ減らす演算子です。
こちらも変数--
または--変数
の2通りがあります。
例: 1だけ減らす
#include <stdio.h>
int main(void) {
int y = 3;
printf("初期値 y = %d\n", y);
y--; // y を 1 減らす(2になる)
printf("y を 1 減らした後: %d\n", y);
return 0;
}
初期値 y = 3
y を 1 減らした後: 2
使い方のルール(変数だけ)
++
と--
の対象は「変更可能な左辺値(modifiable lvalue)」に限られます。
簡単に言うと、代入できる場所だけに使えます。
OK/NGの代表例
種類 | 例 | 可否 | 補足 |
---|---|---|---|
変数 | i++; | OK | 最も基本です |
配列要素 | a[i]++; | OK | 要素は変数と同様に扱えます |
構造体メンバ | user.count++; | OK | 変更可能なメンバならOK |
ポインタが指す先 | (*p)++; | OK | 参照先を書き換えます |
関数の戻り値 | f()++; | NG | 戻り値は一時値で左辺値ではありません |
定数 | 10++; | NG | 定数は変更できません |
計算結果 | (a + b)++; | NG | 一時的な値は変更できません |
「変数の入れ物が実在するもの」だけに使えると覚えると分かりやすいです。
整数型で使うのが基本
C言語では++
/--
は整数型にも浮動小数点型にも使えますが、初心者のうちは整数型(int, long, size_tなど)での使用に限定するのが安全です。
浮動小数点数は丸め誤差があり、意図しない微小な誤差が蓄積することがあります。
カウンタや回数の概念は整数で表すのが定石です。
本記事の範囲(前置/後置は別記事)
前置(++i, --i)
と後置(i++, i--)
は「評価結果の値」が異なるという重要な話題がありますが、本記事では違いを掘り下げません。
ここでは「1増える」「1減る」という効果だけを使い、式の値としての違いは別記事で丁寧に扱います。
++/–の使いどころ
カウンタ変数を1ずつ増やす
イベント発生回数や処理件数を数える用途が基本です。
状態を足し合わせるような場面は++
が自然に読めます。
#include <stdio.h>
int main(void) {
int processed = 0;
// 3つの作業を終えるたびにカウンタを1増やす
processed++; // 1件目完了
processed++; // 2件目完了
processed++; // 3件目完了
printf("処理件数: %d\n", processed);
return 0;
}
処理件数: 3
for文のループで++を使う
決まった回数の繰り返しはfor
の第3式に++
をまとめるのが読みやすく、安全です。
#include <stdio.h>
int main(void) {
// 0から4までを出力
for (int i = 0; i < 5; i++) { // ここで i++ と書くのが定石
printf("%d ", i);
}
printf("\n");
return 0;
}
0 1 2 3 4
while文で–して0まで回す
残り回数を--
で減らしながら、0になるまで繰り返すのはよくあるパターンです。
判定と更新は分けると読みやすくなります。
#include <stdio.h>
int main(void) {
int remain = 3;
while (remain > 0) { // 残りがある間ループ
printf("残り=%d\n", remain);
remain--; // 1ずつ減らす
}
printf("ループ終了 (remain=%d)\n", remain);
return 0;
}
残り=3
残り=2
残り=1
ループ終了 (remain=0)
配列インデックスの更新に++/–
配列の走査ではインデックスをi++
で進めるのが自然です。
インデックスは0始まりなので、境界条件にも注意します。
#include <stdio.h>
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += a[i]; // i は各反復の最後で i++ により1増える
}
printf("合計: %d\n", sum);
return 0;
}
合計: 15
回数や残り数の更新に使う
進捗表示や残タスク数など、「増えた件数」「減った残り」を即時に反映できます。
#include <stdio.h>
int main(void) {
int done = 0;
int total = 4;
// 2件処理が完了したと仮定
done++; // 1件完了
total--; // 残りを1減らす
done++; // 2件完了
total--; // 残りを1減らす
printf("完了:%d件, 残り:%d件\n", done, total - done);
return 0;
}
完了:2件, 残り:0件
初心者がやりがちなNG例と注意点
同じ式で同じ変数を2回以上++/–しない
同一式の中で同じ変数を複数回++
/--
すると、Cでは動作未定義になるケースがあります。
たとえば次は危険です。
// NG例: 動作未定義になる可能性がある式
// i を同じ式の中で2回以上変更している
int i = 0;
// i = i++ + ++i; // UB(動作未定義)
// printf("%d %d", i++, ++i); // UB(動作未定義)
修正案は、1文1操作に分解することです。
int i = 0;
int a = i; // まず値を取り出す
i++; // ここで1回だけ増やす
int b = i; // 増えた後の値を使う
// 以降、a と b を安全に使える
関数の戻り値や定数に++/–は不可
f()++
や++10
のように「左辺値ではないもの」はエラーです。
// NG例: いずれもコンパイルエラー
// ++10;
// (a + b)++;
// get_value()++; // 戻り値をインクリメントは不可
参考までに、典型的なエラーメッセージは以下のようになります(コンパイラにより異なります)。
error: lvalue required as increment operand
複雑な式での++/–は避ける
たとえ文法上は正しくても、読み手が意図を誤解しやすい書き方は避けるべきです。
// NG例: 条件とインデックスの更新が絡んで読みにくい
// if (arr[i++] == key && --n > 0) { ... }
意図ごとに分けて書くと安全です。
// OK例: 「比較する」「更新する」を分割
int v = arr[i];
if (v == key && n > 0) {
i++; // インデックスを進める
n--; // 残回数を減らす
// ...
}
整数オーバーフローに注意
符号付き整数のオーバーフローはCでは動作未定義です。
INT_MAX
などの上限付近で++
/--
する場合は境界を確認しましょう。
#include <stdio.h>
#include <limits.h>
int main(void) {
int x = INT_MAX;
// 危険: x++ はオーバーフロー(動作未定義)になる
// x++;
// 安全策: 増やす前にチェック
if (x < INT_MAX) {
x++;
printf("安全に増やせました: %d\n", x);
} else {
printf("これ以上増やせません(INT_MAX=%d)\n", INT_MAX);
}
return 0;
}
これ以上増やせません(INT_MAX=2147483647)
必要に応じてunsigned
やsize_t
を使う選択肢もありますが、負の値を使う可能性があるなら安易に符号なしにしないことが大切です。
読みやすく1文1操作にする
「読みやすさ」もバグ予防の一部です。
以下のように分割すると意図が明快になります。
// NG例
// if (++i < n && arr[i++] == key) { ... }
// OK例: 1文1操作へ分割
i++; // 目的1: i を増やす
if (i < n) { // 目的2: 範囲チェック
int v = arr[i]; // 目的3: 取り出す
if (v == key) {
i++; // 目的4: 次の要素へ進める
// ...
}
}
++/–を安全に使う書き方のコツ
ループの増減はforの第3式にまとめる
反復の増減はfor
ヘッダの第3式に置くと、ループ本体の処理ロジックと更新ロジックを分離でき、意図が明確になります。
for (int i = 0; i < n; i++) {
// 本体: 仕事だけを書く
}
++/–は単純な行に分けて書く
評価順序の誤解や動作未定義を避けるため、++
/--
は単純な行で書くのがおすすめです。
// 推奨
count++; // 増やす
index--; // 減らす
変数名で意図を伝える(countやindex)
名前は設計です。
count
やindex
のように、増減の意味が自然に伝わる名前にすると、++
/--
の意図が読み手にすぐ伝わります。
int count = 0; // 件数
int index = 0; // 配列の位置
count++; // 件数が1増えた
index++; // 次の要素へ
境界値を確認する(0, 1, 最大値)
0や1、上限値はバグの温床です。
i >= 0
やi < n
などの条件、i == 0
到達時の処理、INT_MAX
近傍の増減は意識的にチェックしましょう。
#include <stdio.h>
#include <limits.h>
int main(void) {
int i = 0;
int n = 5;
// 下限(0)と上限(n)をしっかり守る
for (i = 0; i < n; i++) {
printf("%d ", i);
}
printf("\n");
// 減らす場合は負にならないよう確認
int remain = 2;
if (remain > 0) {
remain--;
}
printf("remain=%d\n", remain);
// 上限チェックの例
int x = INT_MAX;
if (x < INT_MAX) {
x++;
} else {
// 上限に達したときの代替処理
// 例: ログを出す、加算をスキップする、エラーにする など
}
return 0;
}
0 1 2 3 4
remain=1
まとめ
++
と--
は「1ずつ増減」を簡潔に表現できる強力な道具です。
特にカウンタ更新やループ制御と相性が良く、for
の第3式や配列走査で自然に使えます。
一方で、同じ式での多重更新や境界値未確認、関数戻り値や定数への適用はNGです。
初心者の方は1文1操作、変数名で意図を明示、境界チェックという基本を守り、前置/後置の違いは別記事でしっかり理解すると、安全で読みやすいCコードが書けるようになります。