この記事では、私がポインタをまとめる次元を、四文字で要約すると、「両己三他」です!読んでいてまったく口調が良くないし、韻も踏んでいないですね。何だこれ。この「両己三他」を詳しく言うと、己址、己値、他値、他址、他型です。
私はこの5つの次元からポインタについてもう一度話せると思います。ただし、話す前に、ポインタの「両己三他」の次元をすべて含むプログラムを書きました。それから一つ一つの次元の意味を説明していきます。あなたもそうだと思いませんか?
ポインタを使うほとんどの場面では、この5つの次元で十分に理解する助けになるはずです。ただし、ポインタを使う特別な場面では、5次元法では役に立たないかもしれません。
この先は長文です。読んでいて飽きてしまったら、この記事をブックマークして、時間があるときに続きを読んでください。
一、プログラムコード
1.1. コード
例
実行結果は次のとおりです:

私は自分の記事で自分が理解していることを説明するとき、非常に簡単なプログラムを使って明らかにするのが好きです。だから、難解なプログラムでなければ説明にならないと考える人や、低レベルなプログラムを軽蔑する達人たちは、私を無視してください^_^。
2.2. int型変数para
プログラム中:
int para = 1;
printf("变量para自己的地址是: 0X%x\n", ¶);
printf("变量para自己的值是: 0X%x\n", para);
変数paraが定義されています。それには自分自身のデータ値と、自分自身の格納アドレスがあります。これらはどれも簡単に理解できます。実行結果から見ると、変数para自身のデータ値は16進数の「0X1」で、アドレスは16進数の「0X22feb4」です。言い換えると、メモリ内でアドレス「0X22feb4」から始まる記憶領域に、4バイトでデータ値「0X1」が格納されています。私のマシンでは、int型変数は4バイトを占有します。あなたのマシンでは、私と異なるかもしれません。
int型変数paraについては、誰でも理解しやすいでしょう。次に、変数paraがメモリ内に格納されている状況を示す模式図を描きます。次のとおりです:

次に、「両己三他」の概念について説明します。
二、両己三他
「両己三他」を詳しく言うと、己址、己値、他値、他址、他型です。
2.1. 己址
2.1.1 「己址」の概念
「己址」とは、「自分のアドレス」の略です。ポインタpIntは変数として、int変数paraと同じように、メモリ内の一定の記憶領域に格納される必要があり、この記憶領域にも開始アドレスがあります。つまり、ポインタ変数pIntにも自分自身のアドレスがあるということです。先ほど、変数paraのアドレスは「0X22feb4」であると言いました。では、ポインタ変数pIntのアドレスは何でしょうか?
2.1.2 「己址」の取得
私たちは皆、「&」がアドレスを取得する演算子であることを学びました。プログラム中では:
printf("指针变量pInt自己的地址是: 0X%x\n", &pInt);
「&」を使ってポインタ変数pIntのアドレスを取得します。実行結果から見ると、ポインタ変数pIntのアドレスは「0X22feb8」です。私のマシンでは、ポインタ変数pIntも4バイトを占有します。したがって、ポインタ変数pIntは開始アドレス「0X22feb8」から始まる4バイトの領域に格納されます。
2.1.3 「己址」のコードでの書き方
コード中で、ポインタ変数pIntの「己址」を表すコードの書き方として、よくあるのは:
&pInt;
さて、先ほどの模式図を完成させましょう。図にポインタ変数pIntの「己址」を加えて、ポインタ変数pIntがメモリ内でどのような格納形式であるかを示します。

「己址」とは、こういう意味です。ほら、特に難しくないでしょう!
2.2 己値
2.2.1 「己値」の概念
「己値」とは、「自分のデータ値」の略です。ポインタpIntは変数として、変数paraと同じように、自分自身のデータ値を持っています。
2.2.2 「己値」の取得
先ほど、変数para自身のデータ値は「1」であると述べました。では、ポインタ変数pInt自身のデータ値は何でしょうか。プログラム中では:
pInt = ¶
printf("指针变量pInt自己的值是: 0X%x\n", pInt);
私は「&」演算子を使って、変数paraのアドレス値をポインタ変数pIntに代入し、printfによってポインタ変数pIntのデータ値を出力しました。実行結果から見ると、ポインタ変数pInt自身のデータ値は「0X22feb4」です。さらに、変数paraのアドレスも「0X22feb4」です。したがって、
pInt = ¶
この文の本質は、変数paraのアドレスをポインタ変数pIntの己値に与えたことであり、これによってポインタ変数pIntと変数paraが結び付けられました。
「己址」の中で述べたように、ポインタpIntのデータ値はアドレス「0X22feb8」から始まる4バイトのメモリに格納されています。つまり、アドレス「0X22feb8」から始まるメモリの後続4バイトが、データ値「0X22feb4」を格納するために使われているということです。
2.2.3 「己値」のコードでの書き方
コード中で、ポインタ変数pIntの「己値」を表すコードの書き方として、よくあるのは:
pInt;
また、次のようなコードの書き方もあります:
pInt + N; pInt - N;
この書き方は、pIntの「己値」に数値Nを加える、または数値Nを引くという意味です。これについては「他型」という属性を説明するときに触れます。次のような書き方もあります:
pIntA - pIntB;
この書き方は、2つのポインタ変数が「己値」で減算を行うことを表します。
2.2.4 模式図
さて、引き続き上記の模式図を完成させましょう。ポインタ変数pIntの己値を加えます。

したがって、一般的に言えば、「己値」はポインタ変数pIntにとっては自分自身のデータ値であり、他のint型の変数にとってはそれらのアドレスです。
2.3 他址
2.3.1 「他址」の概念
「他址」の概念は、「他人のアドレス」という意味です。実は先ほど己値について述べたとき、すでにそれとなく「他址」の概念に触れていました。
2.3.2 「他址」の取得
整数型変数paraは、メモリアドレス「0X22feb4」から始まる4バイトに格納されています。プログラム中で、私は次のようにして
pInt = ¶
変数paraのアドレスをポインタ変数pIntに代入することで、ポインタ変数pIntと変数paraが結び付けられました。より本質的に言えば、「他人のアドレス」をポインタ変数pIntの「自身の値」に代入したのです。ここで、「他人のアドレス」の「他人」とは変数paraを指し、「他人のアドレス」の「アドレス」とは変数paraのアドレスを指します。注意してください。「他人のアドレス」と「自身の値」はデータ値として同じです。では、何か気づきましたか?
多くの教科書で言うところの「ポインタはアドレス変数であり、他の変数のアドレスを格納する」というのは、要するに「他址」という次元のデータ値が「己値」という次元のデータ値と等しいと言っているだけであり、教科書がそこまで明確に説明していないだけです。
2.3.3 模式図
もう一度、あの模式図を完成させましょう。今回は「他址」の概念を加えます。

2.4 他値
2.4.1 「他値」の概念
「他値」とは、「他人のデータ値」という意味です。
2.4.2 「他値」の取得
プログラム中で、私は次のようにして
pInt = ¶
変数paraのアドレスをポインタ変数pIntの「自分自身の値」に代入することで、ポインタ変数pIntと変数paraが結び付けられました。このとき、「他人のデータ値」の「他人」とは変数paraを指し、「他人のデータ値」の「データ値」とは変数paraのデータ値「1」を指します。プログラム中で、私は次のようにして
printf("指针变量pInt的他值是: 0X%x\n", *pInt);
:これはどうでしょう?実は *(pInt + 1) と同じです。
2.4.3 「他値」のコードの書き方
あなたがコードでよく見かけるああいったコードの書き方、例えば *pInt のような書き方は、何を意味しているのでしょうか?実は、ポインタ変数pIntの「他値」を計算しているのです!
そうした書き方とは:*(pInt + 1)、*pInt + 1、pInt
*(pInt + 1):もし pInt + 1 を別のポインタと見なすなら、例えば
int *pTemp = pInt + 1;
では *(pInt + 1) が計算しているのは、本質的にはポインタ変数pTempの「他値」です;
*pInt + 1:これは pInt の「他値」に1を加えることです;
pInt
2.4.4 模式図
上の模式図をさらに完成させます。今回は「他値」の概念を加えます:

2.5 他型
2.5.1 「他型」の概念
「他型」とは、「他人の型」の略称です。プログラムの中で、ポインタ変数pIntを宣言するとき、次のように書きます:
int *pInt = NULL;
ポインタ変数pIntの前にある「int」は、ポインタ変数pIntの「己値」がint型のデータ値であることを言っているのではありません。そうではなく、ポインタ変数pIntの「他値」がint型のデータ値であることを言っているのです。ここでのポインタ変数pIntの「他値」は変数paraのデータ値「0X1」です。したがって、ポインタ変数pIntの前にある「int」が指すのは、データ値「0X1」が「int」型であるということです。
要するに、ポインタを宣言するときの型は「他値」を修飾するものであり、「己値」を修飾するものではありません。
さらに、変数paraを宣言するときを見てください:
int para = 1;
変数paraの前にある「int」は、変数paraの型が整数型であることを指します。このときの「int」はparaにとって「自型」、つまり「自分自身の型」という意味です。ポインタの宣言時の型だけが「他型」、つまり「他人の型」なのです。
「他型」が「他値」を修飾するものなら、ポインタを宣言するときにこの「他型」を付けることにどんな意味があるのでしょうか?続けて見てください!
プログラムの中で、次のようなコード断片:
int arr_int[2] = {1, 2};
pInt = arr_int;
printf("arr_int第一个元素arr_int[0]的地址是: 0X%x\n", pInt);
printf("arr_int第二个元素arr_int[1]的地址是: 0X%x\n", pInt + 1);
整数型配列arr_intのアドレスをポインタ変数pIntに代入しました。するとpIntの「己アドレス」は変わらず、まだ0X22feb8ですが、「己値」は変わりました。
さっきポインタ変数pIntの「己値」は「0X22feb4」、つまり変数paraのアドレスでしたが、今は「0X22feac」になりました。これは配列arr_intの最初の要素のアドレスです。つまり、ポインタ変数pIntの「己アドレス」は変わりませんが、「己値」は変更できるということです。
さて、「pInt」と「pInt + 1」の違いを見てみましょう。これはpIntの「己値」を使って演算しています。実行結果を見ると、pIntの「己値」はこのとき「0X22feac」で、pInt + 1の「己値」は「0X22feb0」です。気づきましたか?両者はちょうど4バイト違い、そして「int」型のデータもちょうど4バイトを占めます。
pInt + 1は「己値」に1を足すのだから、「0X22feac + 1」=「0X22fead」になるはずだと思うかもしれません。なぜそうならないのか?それはポインタ変数pIntの「他型」の仕業です。
「他型」の意味を、わかりやすく言うとこうです。「おい、pIntよ。お前の他値はint型のデータ値だ。これからお前が己値に+1、+2、あるいは-1、-2をするとき、馬鹿みたいに本当に1バイト、2バイト足したり、本当に1バイト、2バイト引いたりするんじゃないぞ。int型は4バイトを占めているんだから、4バイトを1単位として、1 * 4バイト、2 * 4バイトを足すか、1 * 4バイト、2 * 4バイトを引くんだ。分かったか?あ、ちなみに、pInt + NのNは正でも負でも構わない」
もちろん、お使いのマシンで「int」型が8バイトを占めるなら、pInt + 1は「己値」に8バイト加えることであり、pInt + 2は「己値」に 8 * 2 = 16バイト加えることです。そういう意味です。
プログラムの中で、この「他型」を説明するための例をもう一つ挙げました。プログラムは次のとおりです:
double *pDouble = NULL;
double arr_double[2] = {1.1, 2.2};
pDouble = arr_double;
printf("arr_double第一个元素arr_double[0]的地址是: 0X%x\n", pDouble);
printf("arr_double第二个元素arr_double[1]的地址是: 0X%x\n", pDouble + 1);
今回はポインタ変数pDoubleを宣言します。その「他型」は「double」型で、「己値」は配列arr_doubleのアドレス、「他値」は配列arr_double
3. まとめ
そろそろまとめる頃合いです。
ポインタ変数を一つ宣言します:
type *pType = NULL;
pTypeには5つの次元があります。それぞれ:
pType = (己アドレス,己値,他アドレス,他値,他型);
3.1 己址:すなわち「自分のアドレス」
ポインタ変数pTypeは変数として、自分自身のアドレスもあります。よくあるコードの書き方は『&pType』です。
己アドレスは一般的なプログラムでは頻繁には使われません。使うとすれば『ポインタのポインタ』に関わってきますが、これはまた別の話題なので、本稿では触れません;
3.2 己値:すなわち「自分のデータ値」
ポインタ変数pTypeは変数として、自分自身のデータ値もあります。コードの書き方は『pType』です。
また、己値に対して加減算を行うこともできます。よくあるコードの書き方には『pType + N』、『pType - N』、『pType2 - pType1』などがあります。
3.3 他址:すなわち「他人のアドレス」
ポインタ変数pTypeの己値は、自分自身のデータ値を表すだけでなく、pTypeに結び付けられた『type』型の変数のアドレスも表します。一般に、ポインタ変数pTypeの『己値』と『他アドレス』はデータ値としては同じです。
type型の変数をpTypeに結び付ける一般的な方法は:pType = &変数;です。
3.4 他値:すなわち「他人のデータ値」
type型の変数がpTypeに結び付けられると、ポインタ変数pTypeはいくつかのコードの書き方によって、type型変数の値、つまり『他値』を取得できます。よくあるコードの書き方には、『*pType』、『pType->』などがあります。
そして、これらのコードの書き方、『*(pType + N)』、『*(pType - N)』、『pType[N]』も『他値』を取得するものですが、特別に説明しておく必要があります:
pType + N は次のように見なせます:
type *pTemp = pType + N;
『*(pType + N)』が実際に計算しているのは、ポインタ変数pTempの『他値』です。
『*(pType - N)』は、理解しやすいでしょう;
『pType[N]』は実は『*(pType + N)』と同じです。丸暗記してください。
3.5 他型:すなわち「他人の型」
ポインタ変数pTypeを宣言するとき、前にある『type』はpTypeの『己値』を修飾するものではなく、『他値』を修飾するものです。つまり、『type』はpTypeの『己値』がtype型のデータ値だと言っているのではなく、pTypeの『他値』がtype型のデータ値だと言っているのです。
『他型』のコードにおける役割は、主に『pType + N』、『pType - N』を計算するときに、pTypeが ( N * sizeof(type) ) バイトを加算または減算することです。
ポインタはいつも人をクラクラさせますが、おそらくこの5つの次元のうちの1つまたはいくつかでクラクラさせられているのでしょう。この5つの次元をしっかり理解し尽くせば、ポインタなんてただの張り子の虎です。
4. 練習問題の解説
5つの次元を説明し終えたら、実際に手を動かす練習問題がなければいけませんね。以下に、ポインタに関連する練習問題をいくつか挙げます。どれも初心者をクラクラさせてダウンさせるものです。この5つの次元を使ってこれらの問題を解きほぐしていくので、少しは楽になるかどうか見てください!
4.1 配列要素の合計
4.1.1 プログラム
最初の例題はよくあるプログラムで、配列要素の合計を求めるものです。プログラムは次のとおりです:
例
プログラムはとても簡単で、まず配列の全要素を出力し、それから配列の全要素の合計を計算します。実行結果は次のとおりです:

4.1.2 「二己三他」による解釈
4.1.2.1 配列要素の出力
配列要素を出力するとき、コードは次のとおりです:
pArr = arr;
printf("%d ", pArr[index]);
このコードは次のものと同等です
pArr = arr;
printf("%d ", *( pArr + index));
ここでは『己値』と『他型』を使って加算演算を行い、『他値』を使って配列要素を取得しています。
『己値』:コードはまず配列名arrのデータ値をpArrの『己値』に代入します。配列名arrのデータ値は何でしょうか?arr
のアドレスです。
int *pTemp = pArr + index;
のアドレスです。
「他型」:pArrの他型は「int」型です。pArr + indexは、pArrの己値に何バイト加えるのでしょうか?pArrの他型はint型なので、pArr + indexはpArrの己値に index * sizeof(int) バイトを加えることを意味するのではないでしょうか!
pArr:pArr + 0と書くことができます。つまり 0 * 4 = 0バイトを加えることになり、このときpTempの己値はarr
pArr + 1:1 * 4 = 4バイトを加えることになり、このときpTempの己値はarr
pArr + 2:2 * 4 = 8バイトを加えることになり、このときpTempの己値はarr
このようにして、pArr + indexで配列の全要素のアドレスを走査できます。
お気づきのとおり、pTempの己値は変化し続けますが、pArrの己値と己アドレスは常に変わりません。
「他値」:pArr + indexで配列の全要素のアドレスを走査できるのであれば、*(pArr + index)、つまり*pTempを使えば、pTempの他値を取得できるのではないでしょうか。こうして配列の全要素の値も走査できるのです!
4.1.2.2 配列要素の合計
配列要素の合計を求める場合に使用するコードは次のとおりです:
sum = sum + *(pArr + index);
先ほどの配列要素の出力に関する分析から、このコードの中でpArrがどう動くか、みなさんもお分かりですよね!
pArr + indexは依然としてpArrの己値を使って加算を行い、一時ポインタpTempの己値を取得します。このpTempの己値は各配列要素のアドレスです。
さらに *(pArr + inedx)、つまり*pTempを使って、一時ポインタpTempの他値、すなわち各要素の値を取得します。
最後に、pTempの各他値をすべて合計して、配列要素の和を算出します。
4.1.2.3 まとめ
みなさん、「二己三他」の観点から、pArr、pArr + index、*(pArr + index)、pArr[index]、*pArr + indexなどの一般的なコードの書き方を理解してみてください!
4.2 ポインタ配列
ポインタ配列はポインタと配列を組み合わせたもので、初心者の方には理解しにくいかもしれません。ポインタ配列はかなり大きなテーマです。関連概念については一般的なC言語の教科書を参照してください。ここでは「二己三他」の概念を使って、プログラム中のポインタ関連の概念を説明するだけに留めます。
4.2.1 プログラム
ポインタ配列について、次に例を挙げます:
例
実行結果は次のとおりです:

4.2.2 「二己三他」による解釈
4.2.2.1 すべての文字列を出力する
まずポインタ配列の定義を見てください:
char *arr[3] = {"abc", "def", "ghi"};
この配列の各要素は文字列のように見えますが、実際には本質的に次のようになっています:
char *pChar1 = "abc", *pChar2 = "def", *pChar3 = "ghi";
char *arr[3] = {pChar1, pChar2, pChar3};
配列arrの各要素は、実際には「他型」が「char」であるポインタです。
arr
printf("%s ", arr[0]);
本質的には次のようになります:
printf("%s ", pChar1);
pChar1の己値または他アドレスを使用して、文字'a'のアドレスから始まり、後続の'b'と'c'を一つずつ出力します。
pChar2とpChar3についても同様に理解できます。
4.2.2.2 最初の文字列"abc"の各文字を出力する
コードは次のとおりです:
char *pArr = arr[0];
printf("%c ", *(pArr + index) );
arr
pArr + indexは、pArrの己値に index * sizeof(char) バイトを加えて、一時ポインタ変数pTempを生成します:
char *pTemp = pArr + index;
このポインタpTempの己値または他アドレスは、順に文字'a', 'b', 'c'のアドレスになります。つまり、pTempの他値も順に文字'a', 'b', 'c'になります。このようにして、ポインタpTempは文字列"abc"の各文字を順に走査できます。
4.3 連結リスト
連結リストはポインタを最も頻繁に使用します。ノードの挿入や削除などで、次のようなコードの書き方に出くわします:
p2 = p1->next; p1->next = p3->next; ......
なんだこれ、もう頭がくらくらする!こっちのnextポインタ、あっちのnextポインタ、あちこち飛び回って。ああもう、頭の中はノリでいっぱいだ。ふふ、「ポインタ5次元分析法」の登場だ!ただ、連結リストについては、やっぱり別の記事で説明しようと思う。連結リストを説明し終えたら、またこれらのnextポインタを説明するからね。言っておくけど、連結リストの本質なんてそんなものだ。理解してしまえば、連結リストはポインタ以上に張子の虎だ。
著者:石家の魚
出典:https://zhuanlan.zhihu.com/p/27974028