
C言語基礎|添字演算子のオペランド
*p[i] は (p+i)。だから i[p] も同じ意味。Cの添字は“左右入れ替えOK”な2項演算子だよ。
添字演算子 [] は、配列アクセスのための“特別な記法”に見えますよね。
でもC言語では、[] はれっきとした 2項演算子で、仕組みはとても素直です。
ポイントはこの2つです。
- 添字演算子の正体は *(p+i)
- p+i と i+p が同じなので、p[i] と i[p] も同じになる
「え、i[p] って書けるの?」となるところが、この節の面白さです。
ただし、読みにくいので実務では基本使いません(仕組み理解のための知識として持つのが◎)。

添字演算子 [] は2項演算子(オペランドは2つ)
添字演算子は、次の2つのオペランドを取ります。
- 片方:Type 型オブジェクトへのポインタ(Type * の値)
- もう片方:整数型(添字)
そして結果は Type 型の要素そのもの(エイリアス)です。
添字演算子のオペランドと結果
| 形式 | 左オペランド | 右オペランド | 生成されるもの |
|---|---|---|---|
| p[i] | Type *(要素へのポインタ) | 整数 i | Type(要素そのもの) |
| i[p] | 整数 i | Type *(要素へのポインタ) | Type(要素そのもの) |
「順序が任意」というのが、この記事の核心です。
なぜ p[i] と i[p] が同じなの?(正体を展開すると一発)
まず、添字の正体を思い出します。
- p[i] は *(p+i)
そして、加算は可換なので
- p+i と i+p は同じ
だから
- *(p+i) と *(i+p) は同じ
- よって p[i] と i[p] は同じ
等価関係の流れ
p[i]
= *(p + i)
= *(i + p)
= i[p]
8通りの“同じ要素アクセス”を整理
配列名 a も多くの式で先頭要素へのポインタとして扱えるので、同じ要素を指す書き方が増えます。
同じ要素を表す8通り(p が a[0] を指すとき)
| 種類 | 書き方 |
|---|---|
| 配列の添字 | a[i] / i[a] |
| 配列のポインタ算術 + 間接 | *(a+i) / *(i+a) |
| ポインタの添字 | p[i] / i[p] |
| ポインタ算術 + 間接 | *(p+i) / *(i+p) |
この表は「[] は *( + ) の省略」という理解を、見える形にしたものです。
サンプルプログラム(内容・メッセージを変更した例)
ここではよりシンプルな、p[i] と i[p] が同じ要素になるプログラム例です。
プロジェクト名:chap10-13-1 ソースファイル名:chap10-13-1.c
#include <stdio.h>
int main(void)
{
int a[4] = {5, 6, 7, 8};
int *p = a; // p は a[0] を指す
puts("p[i] と i[p] が同じ値になることを確認します。");
for (int i = 0; i < 4; i++) {
printf("i=%d : p[i]=%d i[p]=%d\n", i, p[i], i[p]);
}
puts("次は 2[p] に 99 を代入してみます。");
2[p] = 99;
printf("a[2]=%d *(p+2)=%d\n", a[2], *(p + 2));
return 0;
}プログラムの中身を表で分解して理解する
各式が意味するもの
| 式 | 意味 | ポイント |
|---|---|---|
| p | a[0] を指すポインタ | p は住所(アドレス) |
| p+i | i 個後ろの要素へのポインタ | i 要素ぶん進む |
| *(p+i) | その要素そのもの | 参照外し |
| p[i] | *(p+i) と同じ | 添字は省略表現 |
| i[p] | *(i+p) と同じ | オペランド順は任意 |
でも i[p] は使うべき?(理解用の知識)
i[p] が動くのは仕様として正しいですが、読み手に優しくないです。
実務では p[i] を使うのが自然です。
おすすめ表記
| 目的 | おすすめ | 理由 |
|---|---|---|
| 要素アクセス | p[i] | 読みやすい・一般的 |
| 仕組みの確認 | *(p+i) | ポインタ算術を理解しやすい |
| ネタ・理解確認 | i[p] | 仕様理解の確認用(普段は避ける) |
配列の受け渡し:関数引数の配列 v[] は実はポインタ
ここからが超重要な実戦ポイントです。
関数の仮引数で
- int v[]
- int v[10]
みたいに書けても、Cの規則で *最終的に int v として扱われます。
要素数を書いても、仮引数としては“無視される”イメージです。
関数引数の配列宣言は最終的にポインタ扱い
| 書き方 | 見た目 | 実際の扱い |
|---|---|---|
| void f(int v[]) | 配列っぽい | v は int * |
| void f(int v[10]) | 要素数指定っぽい | v は int *(10は無視される) |
| void f(int *v) | ポインタ | v は int * |
この表は「配列を渡す=先頭要素へのポインタを渡す」というカラクリを整理しています。
サンプル(配列を受け取る関数)を、別の例に変更して解説
先頭 n 個を 1,2,3,... にする関数を用いたプログラム例です。
プロジェクト名:chap10-13-2 ソースファイル名:chap10-13-2.c
#include <stdio.h>
void fill_1toN(int v[], int n)
{
for (int i = 0; i < n; i++)
v[i] = i + 1;
}
int main(void)
{
int a[5] = {0, 0, 0, 0, 0};
puts("配列の先頭から順に 1,2,3,... を入れます。");
fill_1toN(a, 5);
for (int i = 0; i < 5; i++)
printf("a[%d] = %d\n", i, a[i]);
return 0;
}関数呼び出し時のイメージ
呼び出し側: a(= &a[0] 相当)を渡す
+ 要素数 n も渡す
呼び出し先: v はポインタとして受け取り
v[i] で要素を書き換える
なぜ要素数 n を渡す必要がある?
受け渡しているのは配列そのものではなく、先頭要素へのポインタだけだからです。
関数側だけでは「何個ある配列なのか」が分からないので、n を別引数にします。
演習問題
演習10-4:添字と同じ値を入れる関数
要素型が int 型で要素数が n の領域を受け取り、全要素に添字と同じ値を代入する関数 set_index を作成せよ。
関数プロトタイプ:
void set_index(int *v, int n);
解答例
プロジェクト名:chap10-13-3 ソースファイル名:chap10-13-3.c
#include <stdio.h>
void set_index(int *v, int n)
{
for (int i = 0; i < n; i++)
v[i] = i;
}
int main(void)
{
int a[6] = {0};
puts("各要素に添字と同じ値を入れます。");
set_index(a, 6);
for (int i = 0; i < 6; i++)
printf("a[%d] = %d\n", i, a[i]);
return 0;
}解説
v は配列ではなくポインタとして受け取ります。
でも v[i] は *(v+i) の省略なので、配列のようにアクセスできます。
呼び出し側で a を渡すと、a は先頭要素へのポインタとして渡るので、変更がそのまま a に反映されます。
演習10-5:途中から2要素だけ書き換えるとどうなる?
次の呼び出しを行うと、配列 a はどう変化するか。実行して確認し、理由を説明せよ。
fill_1toN(&a[2], 2);
ここで fill_1toN は先頭 n 個に 1..n を代入する関数とする。
解答例(確認用コード)
プロジェクト名:chap10-13-4 ソースファイル名:chap10-13-4.c
#include <stdio.h>
void fill_1toN(int v[], int n)
{
for (int i = 0; i < n; i++)
v[i] = i + 1;
}
int main(void)
{
int a[6] = {10, 20, 30, 40, 50, 60};
puts("部分配列っぽく、途中から2要素だけ書き換えます。");
fill_1toN(&a[2], 2);
for (int i = 0; i < 6; i++)
printf("a[%d] = %d\n", i, a[i]);
return 0;
}想定される結果(例)
- a[2] が 1 に
- a[3] が 2 に
他の要素はそのまま
解説
&a[2] は「a[2] を指すポインタ」です。
それを v として受け取るので、関数内の v[0] は a[2]、v[1] は a[3] を意味します。
つまり配列の途中から“2要素ぶん”を操作したことになります。
このテクニックは便利ですが、n を間違えると配列範囲外を書き換えるので要注意です。
登場する命令(関数)と書式・何をする命令なのか
puts
- 書式:puts(文字列);
- 何をする?:文字列を表示して改行も付ける
printf
- 書式:printf(書式文字列, 引数1, 引数2, ...);
- 何をする?:書式に従って表示する(%d で整数など)
