2.4.1 ポインタと配列の関係 ポインタと配列には密接な関係があります。しかしこれらは別物です。その違いは次の ように要約できます。 「ポインタ宣言はアドレスを記憶するための領域を確保するが、ポインタが指すデータ 領域は確保しない。一方、配列の宣言は要素数のデータ領域を確保する。このとき配列名 は、配列の先頭アドレスを表す定数ポインタとして扱われる」。 これはすでに学んだことの(忘れないための)確認事項と、「配列名は定数ポインタで ある」という新しい規則を述べています。まずは確認事項を整理します。次の配列の宣言、 int a[] = { 1, 2, 3, 4, 5 }; // 宣言時に初期化 は配列の初期化リストの要素の数だけのデータの領域がメモリ内につくられて、その値で 初期化されます。ところが、次のポインタ宣言はエラーとなります。 int *ptr = { 1, 2, 3, 4, 5 }; // エラー ポインタの宣言ではポインタ変数の値(アドレス)の領域しかつくられません。しかも初 期化はアドレスで行わなければならないので構文エラーになります。唯一の例外は文字列 の場合です。 char *ptr = "Que Sera, Sera"; // OK は有効です。この場合は文字列定数が確保され、ポインタはその先頭アドレスを指します。 ただし、この文字列は定数ですから書き換えることができません。 つまりポインタは次のように、ポインタが指すデータ領域が別に確保されているときに 意味を持ちます。 int a[10]; // 10個のint型の連続領域(配列)が確保される int *ptr = a; // ポインタはすでに存在する配列の先頭要素を指す。 は、10個の int型の連続領域が確保されます。そして(int *)型のポインタ変数の領 域が別の場所に確保され、ポインタの値は配列の先頭要素のアドレスになります(初期化)。 ここで配列名はその先頭要素を指す定数ポインタであるということを使っています。これ はこれまで用いてきた表現、 int *ptr = &a[0]; と同じことです。 「配列名はアドレス」であることは実に辻褄が合う話です。ポインタが配列の先頭を指 すときに、添字 i (i = 0〜9)に対して、a[i]と*(ptr + i)とは共に配列の(i+1)番目 の要素(内容)を意味しました。配列要素を添字で参照するa[i]とは先頭アドレス aから iだけ後ろのアドレスの内容ということで、(a + i)というアドレス計算を行っているこ とを意味します。従って、配列はポインタの様に書き表すことができます。つまり a[i] は *( a + i ) と書くことができます。 同じように、ポインタも配列のように扱えます。*( ptr + i ) は ptr[i] と書けます。 この場合、次の表現はすべて同じです( ptrは aを指しているとして)。 a[i], *( a + i), *( ptr + i ), ptr[i], [注]配列を直接参照する括弧[]を添字演算子といいます(つまりこれは演算子です)。 実際、コンパイラは配列要素 a[i]を *(a+i)とコンパイル時に書き換えます。すると配列 の添字番号が0(ゼロ)から始まるわけは理解できます。1番目の要素は、先頭アドレスa から0だけ後ろ(それそのもの)で、 a + 0のアドレスの内容は *( a + 0 )つまり a[0] です。サンプルを示します。
// lst02_04.cpp // 配列とポインタの関係 #include <iostream.h> int main() { int a[] = {1, 2, 3, 4, 5}; int *ptr = a; // &a[0] と同じ int i; cout << "---- 配列要素を添字演算で直接参照する ----" << endl << endl; for(i = 0; i < 5; i++) cout << "a[" << i << "] = " << a[i] << ", "; cout << endl << endl; cout << "---- 配列要素をポインタのようにして参照する ----" << endl << endl; for(i = 0; i < 5; i++) cout << "*(a+" << i << ") = " << *(a+i) << ", "; cout << endl << endl; cout << "---- ポインタで間接参照する ----" << endl << endl; for(i = 0; i < 5; i++) cout << "*(ptr+" << i << ") = " << *(ptr+i) << ", "; cout << endl << endl; cout << "---- ポインタで配列のようにして参照する ----" << endl << endl; for(i = 0; i < 5; i++) cout << "ptr[" << i << "] = " << ptr[i] << ", "; cout << endl; return 0; } |
---- 配列要素を添字演算で直接参照する ---- a[0] = 1, a[1] = 2, a[2] = 3, a[3] = 4, a[4] = 5, ---- 配列要素をポインタのようにして参照する ---- *(a+0) = 1, *(a+1) = 2, *(a+2) = 3, *(a+3) = 4, *(a+4) = 5, ---- ポインタで間接参照する ---- *(ptr+0) = 1, *(ptr+1) = 2, *(ptr+2) = 3, *(ptr+3) = 4, *(ptr+4) = 5, ---- ポインタで配列のようにして参照する ---- ptr[0] = 1, ptr[1] = 2, ptr[2] = 3, ptr[3] = 4, ptr[4] = 5, |
2.4.2 補足:添字番号が1から始まる配列をつくる ポインタは配列名と違って変数ですから、値を変えることができます。このことを使う と、FORTRANでの配列の様にn個の要素からなる配列の添字が1〜nとなるように表現で きます。
// lst02_05.cpp // 配列の添字を1から始めるには #include <iostream.h> int main() { int a[] = {1, 2, 3, 4, 5}; int *ptr = &a[-1]; for(int i = 1; i <= 5; i++) cout << "ptr[" << i << "] = " << ptr[i] << endl; return 0; } |
ptr[1] = 1 ptr[2] = 2 ptr[3] = 3 ptr[4] = 4 ptr[5] = 5 |
Copyright(c) 1999 Yamada,K