前回の予定から行くと、今回は構造体のメンバに文字列の入った物の説明をする予定でしたが、前回までの章を見ていただければ、容易に想像がつきそうなのでやめにして、「ポインタを制する!!」の締めくくりとして、2重ポインタを示してポインタ論を締めくくりたいと思います。
2重ポインタとは、あるポインタ変数のアドレスを指すポインタ変数です。「は?」ですか・・・?プログラムを実行するに当たって、ある変数がメモリ上の領域に割り当てられたとします。その領域を格納することが出来る変数をポインタと言いましたが、その、ポインタのアドレスを格納することの出来る変数が2重ポインタです。下のサンプルを見て下さい。
int a;
int* b;
int** c;
b = &a;
c = &b;
このようにしたとき、「*b」は「a」の値を表しますが、「**c」も「a」の値を表します。ちなみに、「*c」は「a」のアドレスを表します。「c = &b;」なので当然ですね。よって、ポインタ変数「c」が「a」の2重ポインタになっています。
最後のサンプルコードを示したいんですが、その前に、キャストなる物を説明したいと思います。キャストは変数の型変換である、と書いてなるほど、とは言ってもらえないと思います・・・。キャストを使う代表的な例として、malloc 関数を使った時が挙げられます。malloc 関数は引数に指定したサイズ分の連続したメモリ空間を確保し、その先頭アドレスを void 型のポインタで返す関数です。下のサンプルを見て下さい。
int* a;
a = (int*)malloc(sizeof(int)*100);
sizeof(int) 関数で int 型のデータサイズを返します。普通、2バイトもしくは4バイトです。要するに int 型の変数が 100 個保存できる領域を確保し、その先頭アドレスを a と言うポインタに代入しています。ちょうど、int 型の 100 個の配列を宣言したイメージと同じです。そして、
a++;
とすると、ポインタは前から2番目のメモリ空間のアドレスを表し、もう一度、上のオペレーションをすると、同じく3番目のメモリ空間のアドレスを表す、といった感じになります。一回のアドレスのインクリメントによって、アドレスが2バイト分、もしくは4バイト分進んだ訳です。(ポインタが次のメンバを指していると言うことは、そのデータサイズ分、ポインタが進んでいるはずなので。)インデックス番号こそありませんが、配列のポインタと酷似していることが分かったいただけると思います。ただ、配列は動的、即ち、プログラムの実行条件によって、配列のメンバ数を変えるのは不可能でしたが、malloc 関数を使うと条件に応じたサイズのメモリを確保できます。次のサンプルも見て下さい。
char* a;
a = (char*)malloc(sizeof(char)*100);
こんどは、char 型ポインタとして宣言しました。そして、次のオペレーションを実行します。
a++;
今度もやはり、ポインタは前から2番目のメモリ空間のアドレスを表します。即ち、1バイト分、アドレスが進んだことになります。ここで考えてみて下さい。malloc 関数は void 型のポインタを返すはずでした。しかし、実はその void 型のポインタを別のポインタ型に変換している(コンパイラが変換したと認識している。何番地という、メモリ上のアドレスが変わったわけではない。)ので、それぞれのポインタ型にあった、アドレスのインクリメントが実現できているわけです。その、変数の型の変換をキャストといい、ここではアドレス変数のキャストを「(int*)」「(char*)」という、キャスト演算子で実行しています。
下のサンプルは malloc 関数とキャストを用いたサンプルプログラムです。行いたいことは、初めに入力したいデータ数を入力し、そのデータ数だけ整数を入力し、すべてのデータの入力が終わったら、入力された正数の数とその合計、負数の数とその合計を出力するという、プログラムです。入力するデータの数はプログラムの実行のたびに変わるので、配列として宣言するとデータ数が固定されてしまい、ふさわしくないので、malloc 関数を用います。
上のサンプルは実はうちの大学の情報処理演習のレポートの課題です。無理矢理 malloc 関数を使っているので、はっきりいって、もっと単純なやり方もあります。強引に malloc 関数を使っているのであしからず。データを入力すると同時に正負の判断をして、それぞれ合計と累積個数を計算すればいいのでもっと単純に出来ます。説明のためのサンプルです。
さー、いよいよ、最後です。次も強引なサンプルになっています。大規模なプログラムになると構造体の中に何重もの構造体が入っているといった、場合があります。よって、あるデータを使いたいとき、カンマを何個も書いてつなげていくと言った状況になります。が、この場合、よく使うところまでのポインタを宣言し、そのポインタを使ってコーディングをすると言った場合があります。次の例を見て下さい。ちなみに、「...」は「などなど」です。
typedef struct{ /* 名古屋構造体 */
int nakaku;
int nakamuraku;
int higashiku;
...
} nagoya_t;
typedef struct{ /* 愛知構造体 */
nagoya_t nagoya;
...
} aiti_t;
typedef struct{ /* 東海構造体 */
aiti_t aiti;
...
} toukai_t;
typedef struct{ /* 中部構造体 */
toukai_t toukai;
...
} tyubu_t;
typedef struct{ /* 日本構造体 */
tyubu_t tyubu;
...
} japan_t;
typedef struct{ /* アジア構造体 */
jyapan_t japan;
...
} asia_t;
typedef struct{ /* 地球構造体 */
asia_t asia;
...
} earth_t;
(ふー)このような、構造体の定義があったとします。ここで、次のように変数を宣言します。
erath_t earth;
これで、earth_t 型の変数、earth が宣言されました。そして、処理の中で、上記の名古屋市の区別のデータを参照するときは次のようになります。
int a;
a = earth.japan.tyubu.toukai.aiti.nagoya.nakaku;
a = earth.japan.tyubu.toukai.aiti.nagoya.nakamuraku;
a = earth.japan.tyubu.toukai.aiti.nagoya.higashiku;
なんて、すべて書くのは面倒です。ここで、nagoya_t 型のポインタを宣言し、初めだけ、そのアドレスを取得すると、すっきりします。
nagoya_t* nagoya;
int a;
nagoya = &earth.japan.tyubu.toukai.aiti.nagoya;
a = nagoya->nakaku;
a = nagoya->nakamuraku;
a = nagoya->higashiku;
こっちの方がすっきりしています。
nagoya->nakaku = nagoya->nakamuraku;
という代入も、先のようにすべて書いていると下手すると一行では終わりません。このような場合、若しくは、ソースファイルが複数に分かれていて、別のソースファイルで宣言されている変数と同型のポインタを extern で宣言する場合など、構造体のポインタをしばしば用いいるケースがあると想定して、最後のサンプルを見て下さい。ちなみに、このサンプルは解説しません。このようなことが出来ると言うことが理解できれば、とりあえず、C/C++言語でポインタを取り扱うに当たって困るようなことはないと思います。ご健闘を祈ります。