C言語ワークショップ 第3回  配列変数

●はじめに

 配列変数は、その名の通り「列に配られている変数」と言えるでしょう。配列は並んでいなければ配列ではありませんから、変数が法則に従って並んでいるわけです。
 また、配列にポインタを使うと非常に便利な使用法が可能になります。

●キーワード
配列変数英語ではarray
同じ配分のオブジェクトの列などとよく説明される
入るのは「値」にかわりはない
添え字配列ではその配列の並び順番に番号がある、その番号は添え字により
指定される。添え字[0]ような形でつけられる

●配列変数の宣言

 通常の変数では

  char a,b,c,d;

 のようにそれぞれ独立した変数名を指定して、指定した分の変数が、メモリ上に割り当てられたわけですが、これらの変数は4つメモリに割り当てられたとしても、それぞれ独立して割り当てをしているので、お互いには関連がなく、メモリ上に並んで配置される保証は全くありません。
 たとえば

  char a,b;
  int c;
  char d;

 というような宣言がされた場合

メモリイメージ
アドレス変数名
2000ha
2001hb
2002h
2003h
c (下位バイト)
(上位バイト)
2004hd

 のように割り当てられるかもしれませんし、a,b,c,dそれそれが全く違う場所に割り当てられるかもしれません。(ただし変数c自体はint型(2)バイトで一つなのでこれは必ず並びます)

 もしこれがある場所を先頭にして指定したサイズ分、変数が並んでくれたら、それを一つのまとまりとして考えられ、なにかと便利であるわけです。

 そこで配列変数はメモリ上に「その変数達が必ず並ぶ」ように宣言するたものであり、
 宣言は

  int name[n]

 のような形で行われます。

 たとえば

  int d[4]

 となります。これは[]で指定した数の配列の変数を用意するという意味で、この場合はint型の配列変数となり、メモリ上において、この配列が仮に2000hから割り当てられたとしたら

配列変数
アドレス変数名
2000h
2001h
d[0](下位バイト)
(上位バイト)
2002h
2003h
d[1](下位バイト)
(上位バイト)
2004h
2005h
d[2](下位バイト)
(上位バイト)
2006h
2007h
d[3](下位バイト)
(上位バイト)

 と並ぶようになります。

 そして

  char d[8]

 この場合はchar型で8つの配列変数となり、メモリ上において、この配列が仮に2000hから割り当てられたとしたら

配列変数
アドレス変数名
2000hd[0]
2001hd[1]
2002hd[2]
2003hd[3]
2004hd[4]
2005hd[5]
2006hd[6]
2007hd[7]

 と並ぶようになります。

 ここでの注意点として宣言部では

  char d[8]

 といったように8個としていしています。
 そして実際の配列の添え字は[0]からスタートという決まりですからそこから8つ分、数えると、終わりは[7]であることがわかります。

 ここで整理すると

  宣言:  型指定子 name[n];

  参照:  name[0] 〜 name[n-1]

 という形になります。

 もちろんこの応用があり、多次元の配列も可能で

  宣言:  型指定子 name[n][m];
  参照:  name[0][0] 〜 name[n-1][m-1]

 のようにすれば2次元の配列になるわけです。

 また、

  char name[11]="0123456789";

 のような初期化子をつけることによりその配列へ文字を設定することもできます。
 このようなchar型配列へ保存するデータを文字データと考えて格納を行った場合、それを特に「文字列」(もしくは文字配列)と呼びます。

●普通の変数みたいに配列の参照

 ここまでの説明で配列は

  name[0] 〜 name[n-1]

 といった形で参照など行うと説明しました。
 ですから

  name[0]=1;
  a=name[0];

 とすれば配列変数nameの[0]へ1という値を保存したり取り出したり出来るわけで、普通
の変数と比べ特に変わったことはありません。
 また、

  a=2;
  b=name[a];

 のようにすれば、配列の添え字に変数を使うことで変数aを変化させれば配列の中の好きな場所を任意に取り出すことも出来るわけです。

●配列変数にポインタを使う

 さて、配列変数は宣言されると、そのエリアはメモリ上に各要素が並ぶとお話ししました。
 メモリ上に、必ず並ぶと言うことは、ポインタを利用し、ポインタの値でアクセスすることで、メモリ上のデータを連続で出し入れすることもできるわけです。

  char data[11]="01213456789";
  char *po;

  po=&data[0];
  printf("%s",*po);
  po++;
  printf("%s",*po);
  po++;
  printf("%s",*po);

 このように、ます配列変数の先頭アドレス(&data[0])をポインタ変数poへ入れ、あとはpo++でアドレスを加算させていけば、2番目、3番目の配列のデータを表示できるわけです。

 ここで行っていることは

  printf("%s",data[0]);
  printf("%s",data[1]);
  printf("%s",data[2]);

 と何ら変わりはありませんし、また

  int  n=0;
  printf("%s",data[n]);

 としてnの値を変化させることでも同様のことができます。

●配列変数の変数名は特別

 さてここで、もう一つ配列の特有の性質があります。
 たとえば、

  char name[11]="0123456789";

 と宣言した場合、name[0]は'0'、name[1]は'1'が値として参照出来たわけですが、単に

  name

 のように、配列の添え字をつけないとどうなるでしょうか。
 もちろんこのnameは配列変数の変数名として宣言したものなので配列に関わりがあります。  この場合、配列変数名だけで添え字が無いものはその配列の「ポインタ」になります。
 実は、本来、配列とはポインタ変数を使って変数の参照する方法の一つであり、ポインタ変数に[ ]をつけたものを配列と呼んでいるのです。ですから正確には配列のことをポインタの配列参照と言います。(これについては演算子のところで詳しく説明します)
 要するに、この場合は配列変数の先頭アドレス値を返すことになり、変数名だけを見るとパッと見た感じで普通の変数と混乱してしまいやすいで非常に注意が必要です。

 そして返すポインタは

  &data[0]

 と同じその配列変数の先頭アドレスが返ってきます。
 結局、配列変数の場合、先頭のアドレスを得たいなら、単にnameと変数名だけを書いてもいいわけで、

  char data[11]="01213456789";
  char *po;
  po=data;

 のようにdataという配列名のみでアドレスを代入できます。
 もちろん先頭以外のアドレスを得たい場合は添え字をつけ、&で演算して求めればいいわけです。
 この方法は一般的でよく利用されるため、変数名がどのような変数か、どのような値を返すのかを気をつけなければいけません。

●配列変数と文字列

 配列の宣言でふれましたが文字列とは複数の文字をデータ列と考え配列へ入れた場合を指す言葉です。(もちろん入ってるのは値でもあるので、値とみれば値の蛇列ですね)
 そういったことで、単にデータを文字と考えて複数をを並べて保存すればそれは文字列となるわけで、とくに、特別なことでもないと思います。

 さて文字列を変数へ保存したいと思った場合、変数宣言では

  char name[11]="0123456789";

 のような形で初期化出来たわけです。

 普通の変数で、たとえば

  int c;
  c=0;

 であれば、変数cへ値を入れることができました。
 さて、このような方法で文字列を配列へ保存したい場合どうすればいいのでしょうか。
 間違っても

  name="0123";
 や
  name[0]="0123";

 のようには出来ません。
 なぜなら、=(代入演算子)は決まったサイズの値(charやintなど)で代入演算を行うものです。
 ""の中は文字列ですから、char型の数値がいくつも集まったもので全体では型にはまるものではありません。
 じゃどうするか
 面倒だけど

  name[0]='0';
  name[1]='1';
  name[2]='2';
  name[3]='3';

 のようにするしかありません。

 これなら'0'は0という文字なので値としてみれば0x30という数ですから、それを=で代入した(メモリに保存した)ということなので問題ないわけです。
 変数と代入式で、お話ししましたが、=という演算子はあるアドレスからあるアドレスへ値を代入する演算子なので

  a=b;

 だと、aのアドレスへbのアドレスの値を移したったことですから、一度に移動できるのはどう考えても、型で指定されたサイズのデータですよね。
 ですから、char型だと1バイト、int型だと2バイト変数なんで2文字分、long型なら4バイト移せるのことになるわけですから、配列でも面倒だけど一つ一つ移すのです。

 代入演算子ということが理解できたら、こういったやり方も出来ることがわかります

  char *t;
  t=(char *)"0123";

 今度は文字列自体をchar型ポインタとして変数tへ代入しています。  この場合文字列の値(文字データ)自体は移動されていませんが、文字列の先頭アドレスが変数tへ代入されそれが文字列を示していることになります。