今まで配列名そのものは何の意味も持ってこなかった。例えば、以下のように 宣言された配列では、
int array[20];
配列名 array
はプログラム中では何の積極的な意味を持たなかったし、
配列名を利用する方法もなかった。実はタネを明かせば、この配列名はポインタの
ようなものに
なっているのである。つまり、array
は、&array[0]
と同じになって
いるのである。
例 2 int array[20]; printf("Example. address of array\n"); printf("array %u: = &array[0]: %u\n", array, &array[0]); |
この例を実行すれば、まったく同じアドレスを指している事が分かる。 しかし、通常のポインタ変数と違い、配列名へのアドレスの代入は許されていない。 そうした意味で、配列名はポインタと少し違っていて、 あるアドレスを固定的に指す定数であると思えば良い。
間違った例 int array[20]; int a; array = &a; |
参考 この例が間違っている理由は、先に述べたように配列名には代入が許されないからで ある。C 言語では、こうした代入が許されないが、参照は許される(式の右辺には使う 事が出来る) 値を右辺値と呼んでいる。一方、代入が許される(つまり、式の左辺に 使う事が出来る) 値を左辺値と呼ぶ。勿論、左辺値は式の右辺にも使うことが出来るので 左辺値は右辺値を含んでいると言える。
さて、それでは、配列の要素へのアクセスはどのようにしてなされるのだろうか。 前の章で学んだようにポインタへの整数演算は、型に応じてされるので、
array[0]
*array
array[1]
*(array+1)
array[2]
*(array+2)
array[3]
*(array+3)
となっているのである。従って、一般に配列要素とアドレスとの関係は次のように なっている。
array[n] <=> *(array + n) |
そして、C 言語では上の関係は左から右だけではなく、右から左にも有効なのである。 つまり、
例 3 char *s = "array"; int n=0; while(s[n]!='\0'){ putchar(s[n]); n++; } |
というプログラムは、次のプログラムとほぼ同じである事を意味している。
例 4 char s[] = {'a', 'r', 'r', 'a', 'y','\0'}; main(){ char *p=s; while(*p!='\0'){ putchar(*p); p++; } } |
しかし、以前に学んだように配列の初期化は一般には許されず、静的または外的 な場合にのみ許されていることに注意しよう(静的、外的な場合以外でも初期化を 許す処理系もある)。
つまり、C 言語では配列はポインタを用いて実現されているのである。
参考
もっと積極的に言えば、C 言語では、配列要素へのアクセスは常に、
array[n] => *(array+n)
に展開される。このことは次のような奇妙な例を
実行することで明らかとなる。加算は常に交換可能である、即ち
である。従って、
array[n] => *(array+n) => *(n+array) => n[array]
となるので、
参考例 char s[] = {'a', 'r', 'r', 'a', 'y','\0'}; main(){ int n=0; while(n[s]!='\0'){ putchar(n[s]); n++; } } |
は、先の例とまったく同じに正常に動作する!