配列

| Topic path: Top / 授業 / C言語基礎 / 配列

同じ型の変数を複数集めて並べたものを''配列''といいます。

配列に含まれる変数を''要素''といい、配列に含まれる要素の数のことを配列の''要素数''または''長さ''といいます。



*配列の宣言 [#mddc6d6b]

配列を使うには、変数と同じように、使う前に宣言する必要があります。

配列は同じ型の変数が集まってできていますので、配列に含まれる変数の型を配列の型として宣言します。
配列名の後に''角括弧'' ''[ ]'' を付け、括弧の中に要素数を指定します。
#geshi(c){{
  型 配列名[要素数];
}}

たとえば、要素数3のint型の配列 a を宣言すると、次のようになります。
#geshi(c){{
  int a[3];
}}


配列が宣言されると、要素となる変数を規則するのに必要な容量に要素数をかけた分の記憶容量がまとめて確保されます。


*配列の要素にアクセスする [#ie73f304]

配列の要素にアクセスする(値を代入する、値を参照する)には、配列名の後に''角括弧'' ''[ ]'' を付け、括弧の中に''要素番号''を''添字''として指定します(プログラム1)。
#geshi(c){{
  int a[3];

  a[0] = 250;
  a[1] = 350;
  a[2] = 500;

  printf("%d\n", a[0]);
}}
''要素番号が 0 から始まる''ことに注意しましょう。
したがって、''最大の要素番号は要素数よりも1小さい''です。

この配列は、実際のメモリーでは次のように並べて格納されます。
#ref(./array.png,50%)



**演習1 [#ibf8409e]
プログラム1を作成し、実行結果を確認せよ。



*セグメント・エラーに注意 [#if201bbd]

宣言した範囲を超えた要素番号の要素にアクセスすると、(コンパイル時にはエラーになりませんが、)実行時にエラーになることがあります。

このエラーを、''セグメンテーション・フォールト'' (''Segmentation fault'')、''セグメンテーション違反''、''セグメンテーション・エラー''などといいます。
総合情報センターのLinuxサーバーでは、「''セグメントエラー''」と表示されます。

たとえば、次のプログラムについて考えてみましょう(プログラム2)。
#geshi(c){{
  int a[3];

  a[0] = 250;
  a[1] = 350;
  a[2] = 500;

  printf("%d\n", a[10000]);
}}
宣言した配列の要素数は3なので、要素番号の範囲は0から2までですが、このプログラムでは要素番号を10000にしています。

このプログラムを実行すると、次のようになります。
#geshi(sh){{
luna% ./a.out
セグメントエラー
}}

''セグメント・エラーは必ず発生するわけではない''ことに注意しましょう。
たとえば、上の例で要素番号3にしてもセグメント・エラーは発生しません。
配列とアドレスの関係を勉強すると、この理由がわかりますが、ここでは説明しません(C言語応用の授業で勉強します)。

また、セグメント・エラーにならないからといって、宣言した範囲を超えた要素番号を使用してはいけません。
メモリー上のその場所は、他の変数のために使用される可能性があります。

次のプログラム(プログラム3)を実行すると、
#geshi(c){{
  int a[3];
  int i = 12345;

  printf("%d\n", a[3]);
}}
結果は次のようになります。
#geshi(sh){{
luna% a.out
12345
}}



**演習2 [#y7172cc7]
プログラム1をプログラム2、プログラム3に変更し、実行結果を確認せよ。




*配列のすべての要素にfor文でアクセスする [#xbd65674]

for文を使うと、配列のすべての要素に順番にアクセスすることができます。

for文では、0から最大要素番号まで、要素番号を1ずつ増やしながら、配列の要素に繰り返しアクセスします(プログラム4)。
#geshi(c){{
  int a[3];

  a[0] = 250;
  a[1] = 350;
  a[2] = 500;

  for (i = 0; i < 3; i++) {
    printf("%d\n", a[i]);
  }
}}
カウント変数が要素番号を表すので、カウント変数(要素番号)は0から始めて、要素数より小さい間繰り返し処理を行います。
''カウント変数の初期値が 0 である''ことと''繰り返し条件の不等号が <= ではなく < である''ことに注意しましょう。

**演習3 [#c9b6bf09]
プログラム1をプログラム4に変更し、実行結果を確認せよ。



**配列の要素の合計を求める [#bcd70821]

配列のすべての要素にfor文でアクセスして、配列の要素の合計を求めるという処理はよく行われます(プログラム5)。
#geshi(c){{
  int a[] = {1, 2, 3, 4, 5};
  int i, len, sum;

  len = sizeof(a) / sizeof(a[0]);
  sum = 0;
  for (i = 0; i < len; i++) {
    sum += a[i];
  }
  printf("%d\n", sum);
}}

**演習4 [#g2de9919]
プログラム5を作成し、実行結果を確認せよ。



**配列の要素の最大値を求める [#a423fa77]

配列のすべての要素にfor文でアクセスして、配列の要素の最大値や最小値を求めるという処理もよく行われます(プログラム6)。
#geshi(c){{
  int a[] = {3, 5, 2, 4, 1};
  int i, len, max;

  len = sizeof(a) / sizeof(a[0]);
  max = a[0];
  for (i = 1; i < len; i++) {
    if (a[i] > max) {
      max = a[i]
    }
  }
  printf("%d\n", max);
}}
このプログラムでは、最初に先頭の要素 a[0] が最大であると仮定し、その次の要素 a[1] から順にそれまでの最大値と比較してもしその要素 a[i] がそれまでの最大値よりも大きければ最大値にその要素を代入することによって最大値を求めています。
for文のカウント変数が 1 から始まっていることに注意してください。

すべての要素がある値よりも大きいことがわかっている場合には、最大値をその値に初期化し、先頭の要素から順に比較するという方法もあります(プログラム7)。
#geshi(c){{
  int a[] = {3, 5, 2, 4, 1};
  int i, len, max;

  len = sizeof(a) / sizeof(a[0]);
  max = -1;
  for (i = 0; i < len; i++) {
    if (a[i] > max) {
      max = a[i]
    }
  }
  printf("%d\n", max);
}}
このプログラムでは、for文のカウント変数が 0 から始まっています。
最初に max の初期値と a[0] が比較されますが、max の値はどの要素よりも小さいので、必ず a[0] の方が大きくなり、max の値が a[0] になります。
その後の振る舞いは、上のプログラムと同じです。

**演習5 [#oa483061]
プログラム6を作成し、実行結果を確認せよ。

また、プログラム6をプログラム7に変更し、実行結果を確認せよ。





*配列の初期化 [#o441dac2]

配列の値を、宣言時に一度に代入することができます。
これを、配列の''初期化''といいます。

配列を初期化するときは、''波括弧'' ''{ }'' の中に''コンマ'' '','' 区切りで要素を並べ、リストを作って代入します(プログラム8)。
#geshi(c){{
  int i;
  int a[3] = {250, 350, 500};

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


配列を初期化するときに要素数を省略すると、初期化するリストの要素数が配列の要素数になります(プログラム9)。
#geshi(c){{
  int a[] = {250, 350, 500};
}}


配列を初期化するときのリストの要素数が宣言された配列の要素数よりも短いときは、残りの要素には 0 が代入されます(プログラム10)。
#geshi(c){{
  int i;
  int a[5] = {250, 350, 500};

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

**演習6 [#c9968cf9]
プログラム8-10を作成し、実行結果を確認せよ。






*配列の要素数を調べる [#se6b159b]

配列を初期化するときに要素数を省略すると、配列の要素数は代入するリストによって決まります。
そこで、''sizeof演算子''を使って、配列の要素数(長さ)を調べます。

''sizeof演算子''は、変数、配列、型の記憶容量(バイト数)を求める演算子です。
#geshi(c){{
  sizeof(調べる対象)
}}
sizeofは関数のようにも見えますが、演算子です。

sizeof演算子で調べる対象を配列にすると、配列全体のサイズを調べることができます。
また、調べる対象を配列の要素にすると、配列の要素一つ分のサイズを調べることができます。

そこで、この二つを使って、受け取った配列の要素数(長さ)を調べることができます。

次のプログラムは、任意の長さの配列に対して、要素の合計を求めます(プログラム11)。
#geshi(c){{
  int i, len, sum;
  int a[] = {1, 2, 3, 4, 5, 6, 7};

  len = sizeof(a) / sizeof(a[0]);
  sum = 0;
  for (i = 0; i < len; i++) {
    sum += a[i];
  }
  printf("%d\n", sum);
}}
こうすると、配列を初期化するリストを変えても、その他の部分を変える必要がありません。


**演習7 [#u039179c]
プログラム11を作成し、実行結果を確認せよ。


*配列のコピー [#tfb9e206]
**配列は配列に代入できない [#k1232209]

配列を配列に代入することはできません(プログラム12)。
#geshi(c){{
  int a[] = {250, 350, 500};
  int b[3];

  b = a;
}}

**演習8 [#i11517e4]
プログラム12を作成し、コンパイル結果を確認せよ。



**配列をコピーするときはすべての要素をコピーする [#y51aa26e]

配列を配列に代入するときは、for文ですべての要素にアクセスし、値を代入します(プログラム13)。
#geshi(c){{
  int i, len;
  int a[] = {250, 350, 500};
  int b[3];

  len = sizeof(a) / sizeof(a[0]);
  for (i = 0; i < len; i++) {
    b[i] = a[i];
  }
}}
このとき、代入先の配列の長さが代入元の配列の長さより短いと、セグメント・エラーになる可能性があるので注意しましょう。


**演習9 [#rb113fd4]
プログラム12をプログラム13に変更し、実行結果を確認せよ。



*配列と関数 [#l45c2801]

**配列を関数の戻り値にすることはできない [#f6d8c338]

配列そのものを関数の戻り値にすることはできません。

配列が格納されている場所(アドレス、ポインター型の変数)を戻り値にすることによって、実質的には配列を返すことができます。
ただし、ポインターについてはこの授業では勉強しません(C言語応用で勉強します)ので、ここではこのテクニックについては説明しません。



**配列を関数の引数にすることはできる [#xdc23ef4]

配列を関数の引数にすることはできます。

次のプログラムの関数 sum は、int型の配列を引数として受け取って、その配列に含まれる要素の合計を返す関数です(プログラム14)。
#geshi(c){{
int sum(int a[3]) {
  int i, sum;

  sum = 0;
  for (i = 0; i < 3; i++) {
    sum += a[i];
  }

  return sum;  
}


int main(void) {
  int a[3] = {250, 350, 500};

  printf("%d\n", sum(a));

  return 0;
}
}}


**演習10 [#l49a0193]
プログラム14を作成し、実行結果を確認せよ。



**配列を関数の引数にすると要素数は無視される [#l0d27b3a]

実は、配列を関数の引数にすると、要素数を指定しても無視されます。

次のプログラムについて考えてみましょう(プログラム15)。
#geshi(c){{
int sum(int a[3]) {
  int i, sum;

  sum = 0;
  for (i = 0; i < 3; i++) {
    sum += a[i];
  }

  return sum;  
}


int main(void) {
  int a[5] = {10, 20, 30, 40, 50};

  printf("%d\n", sum(a));

  return 0;
}
}}
長さ3の配列を受け取る関数に、長さ5の配列を渡していますが、問題なくコンパイルでき、実行できます。

このため、配列を関数の引数にするときは、要素数を指定する必要がありません(プログラム16)。
#geshi(c){{
int sum(int a[]) {
  int i, sum;

  sum = 0;
  for (i = 0; i < 3; i++) {
    sum += a[i];
  }

  return sum;  
}
}}



**演習11 [#ed40be78]
プログラム15とプログラム16を作成し、実行結果を確認せよ。





**関数に渡された配列の要素数を調べることはできない [#v57e8b41]

実は、配列を関数の引数にすると、配列そのものではなくて、アドレス(ポインター型の変数)として渡します。

配列ではないので、関数に渡された配列に対してsizeof演算子でそのサイズを調べても、配列全体の大きさを調べることができません。

そこで、任意の長さの配列に対応するために、引数に要素数を加えます(プログラム17)。
#geshi(c){{
int sum(int a[], int n) {
  int i, sum;

  sum = 0;
  for (i = 0; i < n; i++) {
    sum += a[i];
  }

  return sum;  
}

int main(void) {
  int a[5] = {10, 20, 30, 40, 50};

  printf("%d\n", sum(a, 5));

  return 0;
}
}}


**演習12 [#wbd1d94c]
プログラム16をプログラム17に変更し、実行結果を確認せよ。





*配列の比較 [#j698ccf9]
**配列は == で比較できない [#ybd18807]

配列は == では比較できません(プログラム18)。
#geshi(c){{
  int a[] = {250, 350, 500};
  int b[] = {250, 350, 500};

  if (a == b) {
    printf("等しい\n");
  } else {
    printf("等しくない\n");
  }
}}
コンパイルはできます。

このプログラムを実行すると、次のようになります。
#geshi(sh){{
luna% a.out
等しくない
}}


**演習13 [#u20ec034]
プログラム18を作成し、実行結果を確認せよ。



**配列を比較するときはすべての要素を比較する [#j8ffd6d2]

配列は == で比較できないので、配列を比較するときはすべての要素を比較します(プログラム19)。
#geshi(c){{
  int i, flag;
  int a[] = {250, 350, 500};
  int b[] = {250, 350, 500};

  flag = 1;
  for (i = 0; i < 3; i++) {
    if (a[i] != b[i]) { flag = 0; break; }
  }

  if (flag) {
    printf("等しい\n");
  } else {
    printf("等しくない\n");
  }
}}


**演習14 [#z2946fa6]
プログラム18をプログラム19に変更し、実行結果を確認せよ。





*2次元配列(配列の配列) [#r90b185e]

**2次元配列の宣言 [#lef7dcd8]
配列は、同じ型の変数を複数並べたものです。

同じ要素数の配列(要素はすべて同じ型)を複数並べると、配列の配列を作ることができます。
これを''2次元配列''といいます。

2次元配列を宣言するには、角括弧 [ ] と要素数の組をもう一つ追加します。
#geshi(c){{
  型 配列名[要素数1][要素数2];
}}

2次元配列の要素にアクセスするには、宣言のときと同様に、角括弧 [ ] と要素番号(添字)の組をもう一つ追加します。

たとえば、要素数3のint型の配列を4つ並べると、3行4列の2次元配列ができます。
#geshi(c){{
  int a[3][4];

  a[0][0] =   0;
  a[0][1] =  10;
  a[0][2] =  20;
  a[0][3] =  30;
  a[1][0] =  40;
  a[1][1] =  50;
  a[1][2] =  60;
  a[1][3] =  70;
  a[2][0] =  80;
  a[2][1] =  90;
  a[2][2] = 100;
  a[2][3] = 110;
}}

この2次元配列を図で表すと、次のようなイメージになります。
#ref(./2d_array.png,50%)

実際のメモリーでは、2次元配列も(1次元の)配列と同じように順番に並べて格納されます。
#ref(./2d_array_in_memory.png,50%)
''行を優先して並べられている''ことに注意しましょう。


**2次元配列の初期化 [#me8c4945]

2次元配列を初期化するときは、リストの中にリストを入れます。
#geshi(c){{
  int a[3][4] = {
    { 0, 10,  20,  30},
    {40, 50,  60,  70},
    {80, 90, 100, 110}
  };
}}

配列を初期化するときには要素数を省略することができますが、2次元配列を初期化するときは先頭の要素数だけが省略可能でき、その他の要素数は省略できません。
#geshi(c){{
  int a[][4] = {
    { 0, 10,  20,  30},
    {40, 50,  60,  70},
    {80, 90, 100, 110}
  };
}}


**2次元配列のすべての要素にfor文でアクセスする [#pd81ed91]

2次元配列のすべての要素にfor文でアクセスするときは、for文をネストします(入れ子構造にします)(プログラム20)。
#geshi(c){{
  int i, j;
  int a[3][4] = {
    { 0, 10,  20,  30},
    {40, 50,  60,  70},
    {80, 90, 100, 110}
  };

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


**演習15 [#b1a67169]
プログラム20を作成し、実行結果を確認せよ。




**多次元配列(おまけ) [#s627d3c3]

2次元配列と同様にして、3次元以上の配列も作ることができます。
#geshi(c){{
  型 配列名[要素数1][要素数2][要素数3];
  型 配列名[要素数1][要素数2][要素数3][要素数4];
}}



----
*まとめ [#g19f41b3]

''配列''は、同じ型の変数を複数並べたものです。

配列を''宣言''するときには、''要素''の''型''、''配列名''、''要素数''を指定します。
配列を''初期化''するときには、要素数を省略できます。

配列の要素の最小の''インデックス番号''は ''0''、最大のインデックス番号は''要素数から1を引いた値''です。
配列のすべての要素にアクセスするときには、for文を用い、カウント変数をインデックス番号として使います。

配列の要素数は、''sizeof演算子''を用いて配列全体の''サイズ''を要素の''サイズ''で割ることによって求めることができます。

配列全体を''コピー''、''比較''することはできません。
配列をコピー、比較するときは、すべての要素を一つずつコピー、比較しなければなりません。

配列を''引数''にして''関数''に渡すことはできますが、配列を関数の''戻り値''にすることはできません。
また、関数の中で引数として受け取った配列の''サイズ''を調べることはできないので、配列を引数にして関数に渡すときはその要素数も一緒に引数と渡します。

''2次元配列''は配列の配列です。
2次元(以上の)配列を初期化するときには、先頭の要素数だけは省略できますが、2番目(以後)の要素数は省略できません。

2次元配列のすべての要素にアクセスするときには、for文のネストを用い、インデックス番号として使用するカウント変数を2種類用意します。


----
*練習問題 [#dd26aea2]
練習問題は[[こちら>/授業/C言語基礎/配列/練習問題]]。
トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS