#「p」のあれこれ
C言語のポインタ学んでたら、「 p」周りで頭がこんがらかってきたので、整理しておく。
まずは基本「*p」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("*p...%d\n", *p);
}
// 出力結果:*p...10
これは簡単。
「p = a」で、pにa[0]のアドレスを入れる。
そして、a[0]の中身を取り出しているだけ。
「*p+1」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("*p+1...%d\n", *p+1);
}
// 出力結果:*p+1...11
これもそう難しくはない。
a[0]の中身を取り出して、それに1を足しているだけだ。
(C言語の文法的な仕様で、「*(参照)」のほうが「+」よりも優先度が高く、先に処理される)
「*(p+1)」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("*(p+1)...%d\n", *(p+1));
}
// 出力結果:*(p+1)...20
さて、今回は括弧が付いた。
括弧は最も優先度の高い演算子である。
よって、「p+1」が先に処理される。
「p+1」は、pが指定する「a[0]」からアドレスを1つ進めるということだ。
すなわち。「a[0+1]」と捉えることができるので、ポインタpは「a[1]」と同値であることが分かる。
これに「*」が付くので、ポインタの指す中身が取り出されて、出力結果は20。
「*p+=1」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("*p+=1...%d\n", *p+=1);
}
// 出力結果:*p+=1...11
これはどうだろう。
「アスタリスク」(参照)は「+=」よりも優先順位が高い。
そのため、*「p」が先に処理される。
「*p」は「*a[0]」と同値であるから、示す値は「10」。
その後、「+=1」されるので、出力結果は11。
ここからが鬼門「*p++」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("*p++...%d\n", *p++);
}
// 出力結果:*p++...10
多くの初学者は、インクリメントしているので「10+1で11」だと思うであろう。私もそうだった。
しかしそれは間違いである。
ここで重要なことは、インクリメントには「前置インクリメント(例:++a)」「後置インクリメント(例:a++)」の2種類があり、その違いを理解することである。
というわけで、先に前置/後置インクリメントの違いを確かめておこう。
...
int a, a_before, a_after;
a = 0;
printf("a...%d\n", a);
// 前置インクリメント
a_before = ++a;
printf("a_before(前置、++a)...%d\n", a_before);
printf("その後のa...%d\n", a);
// 後置インクリメント
a = 0;
a_after = a++;
printf("a_after(後置、a++)...%d\n", a_after);
printf("その後のa...%d\n", a);
...
// 出力結果:a...0
// a_before(前置、++a)...1
// その後のa...1
// a_after(後置、a++)...0
// その後のa...1
こちらのサンプルコードの実行結果から分かるように、
・前置インクリメント→値を渡す前に値が変化
・後置インクリメント→値を渡したのちに値が変化
という違いがある。
より正しく書くと、
後置インクリメントは、「値を利用してから変化させる」と言える。
さて、ここで「* p++」について再び考えてみよう。
// 中略
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("*p++...%d\n", *p++);
// 中略
これは後置インクリメントだ。
先に、ポインタpの参照先である「a[0]」に格納されている値「10」がprintf関数へ渡されて出力される。
ここではじめてpの値が使われる。このあと、格納されている値(アドレス)が進むのである。
ところで、本当に最終的にポインタpのアドレスは進められているのか?実験をしてみよう。
追記してみて、その後の値がどうなっているのか調べてみる。
// 中略
printf("*p++...%d\n", *p++);
// 追加:【実験】その後の値はどうなっているのか?
printf("その後の*p...%d\n", *p);
// 出力結果:*p++...10
// その後の*p...20
これでハッキリとした。
後置インクリメントなので、
**「値を使ってから」
「ポインタ内のアドレスを進める」**のである。
まぁ当然だよね「(*p)++」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("(*p)++...%d\n", (*p)++);
}
// 出力結果:(*p)++...10
これは簡単だ。
さっきの例を見たあとでは混乱するかもしれないが、冷静に考えてみてほしい。
後置インクリメントより、括弧のほうが優先順位が高い。
先に括弧内の処理がされるのである。
驚きの後置インクリメントパワー「*(p++)」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("*(p++)...%d\n", *(p++));
}
// 出力結果:*(p++)...10
ここで思い出してほしいのが、後置インクリメントによって変化するのは、**「値を使われてから」**という特徴である。
printf関数に値が渡されてはじめて値が使われるので、出力結果ではインクリメントされていない状態である。
よって、*p++と同じ動作をする。
ややこしいと感じるなら、以下のように普通のint型変数として考えればわかりやすいだろう。
int a;
a = 0;
printf("a++...%d", a++);
a = 0;
printf("(a++)...%d", (a++));
// 出力結果:a++...0
// (a++)...0
インクリメントの場所変えてみようぜ「*++p」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a [0] = 10, a[1] = 20, a[2] = 30;
printf("*++p...%d\n", *++p);
}
// 出力結果:*++p...20
これは前置インクリメントだ。
前置インクリメントは、値を使う前にインクリメントされる。
つまり、はじめにポインタpがa[0]からa[1]へ進む。
そして、printf関数にポインタaの参照先a[1]の値「20」が渡される。
最後に、一番前でインクリメントしようぜ「++*p」
#include<stdio.h>
int main(void)
{
int a[3], * p;
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("++*p...%d\n", ++*p);
}
// 出力結果:++*p...11
これは単純に「*p」で取り出した値「10」をインクリメントしている。
つまり「11」。
#まとめ
インクリメントの前置・後置の違いを理解すると、何も難しくない。
とても勉強になった。