ポインタについて語ってみた
ポインタ・配列についての「分かりやすい説明」を目指すことは、ネットの解説でも多くみかけますが、ポインタと配列は、根本的に日常とはかけ離れたアクロバティックな頭の使い方を求められるので、日常的直観との距離を縮める比喩的説明(住所を書いたメモを渡す、など)は、ポインタ理解にはあまり役に立たないのでは…と思います。
ポインタを一言で言うと、「住所を書いたメモを渡す」…確かにそうなんです。「ポインタとは何か」なら、その一言で終わってしまうんですが、ポインタに対する需要は「ポインタとは何か」ではなく、「感覚に頼らず論理的にポインタを使えるようになりたい」ことだと思います。
僕が思う解決策としては、そこはもう、とにかくポインタ・配列を使い倒して、身体で覚えるしかないかと。あとは「こんな風に書いたらどう作用するか」という実験を繰り返すことで、自分なりのポインタ理解を育てることが大切かと思います。
int matrix[][5] = {{100, 101, 102, 103, 104}, {200, 201, 202, 203, 204}, {300, 301, 302, 303, 304},}; int (*tbl_p)[ARRAY_NUM(*matrix)] = matrix; for(i=0; i<ARRAY_NUM(matrix); i++){ for(j=0; j<ARRAY_NUM(*matrix); j++) printf("%d ", *(*(tbl_p+i)+j)); printf("\n"); }
例えば、上記のようなコードを書いてる(読んでる)時の、僕の頭の中を言語化すると、以下のような感じになります。
- 「tbl_pは、二次元配列matrixの先頭レコードへのポインタだ。ループカウンタiは、matrixのレコード番号を示している。jは、1レコードのフィールド番号を示している。
- tbl_p+iは、+の左辺が「配列へのポインタ」なので、+iはレコードサイズを1単位として、参照カーソルを移動する演算となる。
- *(tbl_p+i)は「*」によって「現在のレコードへのポインタ」を剥がすので、現在のレコード(配列オブジェクト)を返す。式中の配列オブジェクトは、「その先頭要素へのポインタ」を返す。したがって*(tbl_p+i)は「現在のレコードの先頭フィールドへのポインタ」を返す。
- *(tbl_p+i)+jは、+の左辺が「現在のレコードの先頭フィールドへのポインタ」なので、+jはフィールドサイズを1単位として参照カーソルを移動する演算となる。
- *(*(tbl_p+i)+j)は左端の「*」によって、上記のポインタを剥がす。したがって*(*(tbl_p+i)+j)は、現在のフィールドの値を返す。」
と、こんな風に、富田先生の英文解釈のように(笑)展開規則に従って書いて(読んで)います。
学生時代に通っていた英語学校のGRE Verbalの授業で、「英文中の空欄に当てはまる動詞を選択肢から選べ」という問題があったのですが、GREのVerbalなんて、それはもう日本人にはお目にかかることのない難単語ばかりなんですが、もちろん単語の意味をすべて知っている先生は、単語の意味から適切な答えを割り出していました。
単語の意味を知らない僕は「主語が単数なので、正解は三人称単数のsがついている動詞だ」という解析的な解き方をして、先生になるほど…と感心されたことがあるのですが、昔からこういう「言語を形式的に解析する」作業が好きで、いかに単語の意味をとらずに(ニュアンスなどというあやふやなものに頼らずに)答えを割り出すかにハマっていました。
話が脱線しましたが、よく数学の受験参考書に、三角関数とか微分積分など各分野に特化した参考書ってありますよね。C言語におけるポインタ・配列も、人生のある一時期に「ポインタ・配列について重点的に考える」時期が必要なのではないか、と思います。あ、普通の人じゃなくて、C言語を極めたい奇特な人限定ですが(笑)
★ポインタ・配列に関するおすすめ本とサイト
・『C言語ポインタ完全制覇』…ポインタと言えばこの本。ポインタと配列について考え抜いた、著者の追求思考の痕跡が伺えます。C言語をミソクソに批判する記述が目立ちますが、著者の方は、本当はC言語を愛してやまないんだと思います。じゃないと、ポインタと配列だけでこんなに語れないですよ…。
・『ポインタ虎の巻』…ポインタ完全制覇より難易度高め。
未読ですが、O'Reillyの『詳説Cポインタ』も、ポインタの地頭を鍛えるのにちょうどいい難易度かと思います。
最後に、僕が練習で書いてるコードを載せておきます。ポインタ・配列の素振り練習として、こんなんを毎日繰り返し書いています。
1 #include <stdio.h> 2 3 #define ARRAY_NUM(array) (sizeof(array)/sizeof(*array)) 4 5 int matrix[][5] = {{100,101,102,103,104}, 6 {200,201,202,203,204}, 7 {300,301,302,303,304},}; 8 9 int main(int argc, char **argv){ 10 11 int i,j; 12 13 /* 添字使用 */ 14 for(i=0; i<ARRAY_NUM(matrix); i++){ 15 for(j=0; j<ARRAY_NUM(*matrix); j++) 16 printf("%d ", matrix[i][j]); 17 printf("\n"); 18 } 19 printf("\n"); 20 21 /* 添字不使用 */ 22 for(i=0; i<ARRAY_NUM(matrix); i++){ 23 for(j=0; j<ARRAY_NUM(*matrix); j++) 24 printf("%d ", *(*(matrix+i)+j)); 25 printf("\n"); 26 } 27 printf("\n"); 28 29 /* 先頭部分配列へのポインタ */ 30 int (*ent_p)[ARRAY_NUM(*matrix)] = matrix; 31 32 for(i=0; i<ARRAY_NUM(matrix); i++){ 33 for(j=0; j<ARRAY_NUM(*matrix); j++) 34 printf("%d ", *(*(ent_p+i)+j)); 35 printf("\n"); 36 } 37 printf("\n"); 38 39 /* 二次元配列へのポインタ */ 40 int (*tbl_p)[ARRAY_NUM(matrix)][ARRAY_NUM(*matrix)] = &matrix; 41 42 for(i=0; i<ARRAY_NUM(*tbl_p); i++){ 43 for(j=0; j<ARRAY_NUM(**tbl_p); j++) 44 printf("%d ", *(*(*tbl_p+i)+j)); 45 printf("\n"); 46 } 47 printf("\n"); 48 49 return 0; 50 }