シンプルすぎる配列

こんにちは、めのんです!

前回予告したとおり、今回はCの配列について解説します。
PHPにも配列はありますが、Cの配列はそれとは似ても似つかないものだと思った方がいいでしょう。
添字が整数値のときぐらいでしょうか、何となく似ていると感じるのは。
いいかえると、PHPはキーに整数値と文字列を使えましたが、Cの配列は添数に整数値しか使えません(キーではなく添数ですから)。

だからといって「PHPの知見が活かせないので難しそう」と思う必要はありません。
Cの配列はPHPの配列よりずっと簡単なので覚えることもすくないからです。
ただ、あいまいな覚え方をしてしまうと混乱することもあるので基礎はしっかり固めた方がいいでしょうね。

配列の宣言

配列もオブジェクトなので整数型や浮動小数型のオブジェクトを宣言するのと本質的には変わりません。
ただ、ちょっと癖があるんですよね。
実際の宣言を見てみましょう。

int array1[10];               // int型10要素の配列(初期値無し)
int array2[] = { 1, 2, 3 };   // int型3要素の配列(1, 2, 3の3要素を初期値とする)

これまでに解説したint型やdouble型では、型名のあとにオブジェクト名を書けばそれでよかったのですが、配列の場合は要素数をオブジェクト名のあとに書きます。
配列の型名としてはint [10]のようになるので、配列以外のオブジェクトとはちょっと違いますね。
int [10]であればint型の要素を10個持つ配列ということになります。

それから配列に初期値を持たせる場合は、[] の中の要素数を省略することができます。
上の例では1, 2, 3という3つの要素を持つ配列になりますので、型はint [3]になります。

PHPの配列は自由に要素を追加したり削除することができましたが、Cの配列は宣言時に要素数が決まってしまいます。
そのため、あとから要素を追加したり削除したりすることができません。
ある意味、すごくシンプルな仕様になっています。

初期化子

先ほどの例では配列の初期値として { 1, 2, 3 } を指定していましたね。
このようにオブジェクトの宣言時に初期値を指定するための記述を「初期化子」といいます。
int型やdouble型などほかの型でも、宣言時に = 123 のように初期値を指定することができましたが、これも初期化子です。

配列は複数の要素を持つのが普通です。
そのため波括弧 {} で囲んで複数の初期値を指定します。
配列の要素数が0というのはダメですので、必ず1個は初期値を指定しないといけません。

ちなみにint型など配列以外の初期化子でも波括弧を使えるんですよ。
たとえばこんな感じです。

int x = { 123 };   // OK

こういう書き方はあまり使うことはないんですけど、たまに必要になることがあります。
何らかの形でそういうのも紹介したいと思います。

波括弧で囲んだ初期化子は、配列の要素全部の初期値を指定する必要はありません。
たとえば、要素が10個ある配列の初期化子で3つしか初期値を指定しなければ、最初の3つは指定した初期値になりますが4つめ以降は0で初期化されます。

int x[10] = { 1, 2, 3 };   // { 1, 2, 3, 0, 0, 0, 0, 0, 0, 0 } を指定したのと同じ

もし初期化子の波括弧の中で配列の要素数以上の初期値を並べてしまうとコンパイルエラーになりますので注意が必要です。
また、すべての要素が0でいい場合でも、必ず1個は初期値を指定しないといけません。

要素指示子

先ほど紹介した { 1, 2, 3 } のような初期化子だと、配列の要素を先頭から順に指定するしかありませんでした。
配列の要素は10個あるんだけど、4番目の要素だけ初期値を指定してほかは0でいいということはよくあります。
そんな場合は「要素指示子」を使えばうまくいきます。

int x[10] = { [3] = 123 };      // 添数が3(4番目の要素)だけ123にしてほかは0を埋める
int y[10] = { [4] = 1, 2, 3 };  // { [4] = 1, [5] = 2, [3] = 3 } と同じ
int z[] = { [3] = 0 };          // zの型は int [4]

上の例の1行目で [3] = 123 のように指定していますが、ここでの [3] が要素指示子にあたります。
Cの配列の添数は0から始まりますので3を指定すれば4番目ということになります。

上の例の2行目では [4] = 1 のように要素指示子を使って初期値を指定したあと、要素指示子無しで 2, 3 のように初期値を指定しています。
この場合、要素指示子で添数4の要素を指定していますので、それに続く初期値は添数5、添数6と順番に適用されていきます。
このあたりはPHPの配列と感覚は同じですね。

PHPの配列ではキーを指定して初期化した場合はそのキーの要素だけが追加されますが、Cの場合はあくまでも要素数は添数0から始まります。
ですので、先ほどの例の3行目では [] の中を省略していますが、初期化子で指定した最大の添数によって配列の要素数が決まりますので要素数は4になります。

多次元配列

PHPでもそうですが、多次元配列を作るには配列の要素を配列にすることで実現できます。
こんな風に書きます。

int x[10][3];   // int [3]型の要素を10個持つ多次元配列

上の例のコメントでも書きましたが、この多次元配列はint [3]型の要素を10個持っています。
こう書けばもっとわかりやすいでしょうか。

int (x[10])[3];   // int [3]型の要素を10個持つ多次元配列

多次元配列の初期化子は波括弧を入れ子にします。

int x[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };

もちろん、1次元目にも2次元目にも要素指示子を使うことができます。

配列要素へのアクセス

配列の要素へのアクセスはCでもPHPと同じように行うことができます。
添字演算子([])を使って、0から始まる要素の番号を指定するだけです。

#include <stdio.h>

int main(void)
{
  int a[] = { 1, 2, 3 };
  printf("%d\n", a[1]);  // 2を出力
  a[1] = 7;              // 7で上書き
  printf("%d\n", a[1]);  // 7を出力
  return 0;
}

注意しないといけないのは、添数が範囲を超えたとしてもコンパイル時にエラーが発生することはありません。
つまり何事もなかったかのように変なところにアクセスしてしまうことになります。
たとえマイナスの添数を指定してもコンパイルできてしまいます!

Cよりもっと高級なプログラミング言語では、配列の要素にアクセスを試みるたびに範囲チェックを行っているようです。
それだと安全にはなるんですけど、毎回範囲チェックを裏で行うことになるので遅いですよね。
Cは余計なことをやらないことで高速に動作することが利点になっているので、一長一短があると思います。

配列の全要素に順次アクセス

最後に配列の全要素に順番にアクセスする方法をご紹介します。
といってもPHPでもおなじみの方法だと思いますので何も目新しいことはありません。

#include <stdio.h>

int main(void)
{
  int a[] = { 1, 2, 3 };

  for (int i = 0; i < 3; i++)
  {
    printf("%d\n", a[i]);
  }
  return 0;
}

この講座ではfor文ははじめて登場しましたが、意味も書き方もPHPと同じなのですぐに理解できると思います。
ひとつ覚えておいていただきたいのは、for文の最初でループカウンターのiを宣言していることです。
普通はこういうところでオブジェクトの宣言はできないのですがfor文だけは例外です。

そして、このようにfor文の最初で宣言したオブジェクトはブロック有効範囲を持ちますので、for文の中だけが有効範囲になります。
波括弧の外までブロック有効範囲が及んでいるように見えますが、for文全体がひとつのブロックという解釈だそうです(ちょっと苦しい解釈だとは思います)。

ところで、PHPであれば配列の全要素にアクセスするにはforeach文を使うと思います(状況次第ですし人それぞれ好みはあると思いますが……)。
けれどもCにはPHPのようなforeach文というのはありません。
素直にfor文でループを回すようにしてください。

今回の解説は以上となります。
本当は配列の要素数の取得方法なんかも解説したかったんですけど、ちょっと難しくなるので別の回にゆずることにしました。

次回は文字について解説しようと思います。
どうぞご期待ください!