プログラム実習1・2

ポインタよくあるミス編


今回は、実際に授業を見て、よくあったミスをまとめた後、このあたりの理解を深めるためのちょっとしたコツを紹介します。

このページ内での構造体定義と変数の意味

ここでは次のような構造体を使うことにします。
線形リストによく使う構造体です。

     struct cell {
       int value;
       struct cell *next;
     };

また、特に説明がない限り、aはこの型の構造体変数、pはこの構造体へのポインタ

     struct cell a, *p;

とします。


実体のない領域への参照

構造体へのポインタを作った後、すぐにその先のメンバに代入しようとしている人がいますが、間違いです。

     struct cell a, *p;
     p->value = 10; /* すぐにこれをやってはダメ */

ポインタpは、struct cell型のアドレスを覚えておくだけの変数なので、まだ値を代入する領域が用意されていません。なので、このような代入は実行時エラーになります。

     実体がない・・・。

このpを使って代入したいときは、

     p = &a;

のように、すでに宣言した変数のアドレスを使うか、

     p = (struct cell *)malloc(sizeof(struct cell));

で領域を確保し、そのアドレス(番地)を指して(ポインタに代入して)から値を代入してください。

     実体を用意した。


アロー演算子−>とドット演算子.の使い分け

(*p).valueとp->valueは同じ意味です。プログラムを見やすくするためにも積極的に後者を使うようにしましょう。

     構造体へのポインタ −> メンバ名

で、ポインタが指す領域のメンバを取り出すというわけです。


プログラムを作っている途中でだんだん混乱してきて、

     p.value = 10;

     a->value = 20;

のように書いてしまう人がいますが、両方とも間違いです。ドットは実体に、アロー(矢印)はポインタにつけるものだと覚えておいてください。

ちなみに、ドットとアローの演算順位は同じで、アドレス演算子やポインタ参照演算子より強いです。

    強←  −> =  >  =   →弱

->.が続いた場合、左から順に処理されます。

     a.next->next->value は、( ( (a.next) -> next) -> value)

逆に、*&が続いた場合は、右から処理されます。



ここで問題です。

構造体a, b, cとポインタpが図のようにメモリ上に配置され、それぞれ図のような値が代入されているとします。(変数はメモリ上のどこかに覚えられていて、その場所を「アドレス」というのを思い出してください。)


構造体は、左の青い部分がint型のvalue、右の黄色がstruct cell型ポインタのnextです。
さて、次の変数や式はそれぞれどんな型※のどんな値でしょう?ただしイジワルで間違いも混ざっていますヨ。

ちょっとやりすぎた感はありますが、頑張ってみてください。
(1) p
(2) *p
(3) &p
(4) p.value
(5) (*p).value
(6) *p.value
(7) p->value
(8) p->next
(9) p->next.value
(10) p->next->value
(11) p->next->next
(12) *(p->next)
(13) *p->next
(14) *p->next->next
(15) *(p->next).value
(16) (*(p->next)).value
(17) a
(18) a.value
(19) a.next
(20) a->value
(21) a.next->value
(22) a.next->next
(23) a.next.value
(24) (&a)->next
(25) p->next->next->value
(26) p->next->next->next
(27) p->next->next->next->next
(28) *((*((*p).next)).next)
(29) *p.next
(30) &c
(31) *c
(32) &*&*&c

※どんな型・・・どのような型で宣言した変数に対して代入できるか。


型を意識する。

さて、pも、p->nextも、aも、a.next->valueも、変数や式は全て「型」をもっていることがわかったでしょうか。

プログラムを書いているとき、イマ書いている部分が何を表しているかだけでなく、どんな型をもっているのか(intなのか、int *なのか、struct cellなのか、struct cell*なのか、など)も意識してみるようにすると、この分野においてより理解が深まるでしょう。