授業/C言語基礎/配列 のバックアップ差分(No.7)


  • 追加された行はこの色です。
  • 削除された行はこの色です。
同じ型の変数を複数集めて並べたものを''配列''といいます。

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



*配列を宣言する [#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小さい数です。
''要素番号が 0 から始まる''ことに注意しましょう。
したがって、''最大の要素番号は要素数よりも1小さい''です。

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



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



*配列のすべての要素にfor文でアクセスする [#xbd65674]
*セグメント・エラー [#if201bbd]

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

for文では、0から最大要素番号まで、要素番号を1ずつ増やしながら、配列の要素に繰り返しアクセスします(プログラム2)。
このエラーを、''セグメンテーション・フォールト'' (''Segmentation fault'')、''セグメンテーション違反''、''セグメンテーション・エラー''などといいます。
総合情報センターのLinuxサーバーでは、「''セグメントエラー''」と表示されます。

たとえば、次のプログラムについて考えてみましょう(プログラム2)。
#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]);
  }
  printf("%d\n", a[10000]);
}}
宣言した配列の要素数は3なので、要素番号の範囲は0から2までですが、このプログラムでは要素番号を10000にしています。

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

**演習2 [#c9b6bf09]
プログラム1をプログラム2に変更し、実行結果を確認せよ。
''セグメント・エラーは必ず発生するわけではない''ことに注意しましょう。
たとえば、上の例で要素番号3にしてもセグメント・エラーは発生しません。
配列とアドレスの関係を勉強すると、この理由がわかりますが、ここでは説明しません(C言語応用の授業で勉強します)。

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

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

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

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

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

このエラーを、''セグメンテーション・フォールト'' (''Segmentation fault'')、''セグメンテーション違反''、''セグメンテーション・エラー''などといいます。
総合情報センターのLinuxサーバーでは、「セグメンテーションエラー」と表示されます。
**演習2 [#y7172cc7]
プログラム1をプログラム2、プログラム3に変更し、実行結果を確認せよ。

たとえば、次のプログラムについて考えてみましょう(プログラム3)。



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

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

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

  a[10000] = 0;
}}
宣言した配列の要素数は3なので、要素番号の範囲は0から2までですが、このプログラムでは要素番号を10000にしています。
  a[0] = 250;
  a[1] = 350;
  a[2] = 500;

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

宣言した範囲を超えた要素番号の要素にアクセスしたときに、セグメント・エラーが必ず発生するわけではないことに注意しましょう。
配列とアドレスの関係を勉強するとこの理由がわかりますが、この授業では勉強しませんので、ここでは説明しません。
**演習2 [#c9b6bf09]
プログラム1をプログラム4に変更し、実行結果を確認せよ。


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




*配列を初期化する [#o441dac2]

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

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

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


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


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

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

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



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

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

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

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

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

次のプログラムは、任意の長さの配列に対して、要素の合計を求めます(プログラム8)。
#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);
}}
こうすると、配列を初期化するリストを変えても、その他の部分を変える必要がありません。


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




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

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

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



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

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

次のプログラムの関数 sum は、int型の配列を引数として受け取って、その配列に含まれる要素の合計を返す関数です(プログラム7)。
次のプログラムの関数 sum は、int型の配列を引数として受け取って、その配列に含まれる要素の合計を返す関数です(プログラム9)。
#geshi(c){{
int sum(int[3]) {
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;
}
}}


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



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

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

次のプログラムについて考えてみましょう(プログラム10)。
#geshi(c){{
  length = sizeof(a) / sizeof(a[0]);
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の配列を渡していますが、問題なくコンパイルでき、実行できます。

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

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

  return sum;  
}
}}



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





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

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

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

そこで、任意の長さの配列に対応するために、引数に要素数を加えます(プログラム12)。
#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;
}
}}


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








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

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

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

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

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

たとえば、要素数3のint型の配列を4つ並べると、[math]3 \times 4[/math]の2次元配列ができます。
たとえば、要素数3のint型の配列を4つ並べると、3行4列の2次元配列ができます。
#geshi(c){{
  int i, j;
  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次元配列のすべての要素にfor文でアクセスする [#pd81ed91]

2次元配列のすべての要素にfor文でアクセスするときは、for文をネストします(入れ子構造にします)(プログラム13)。
#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]);
    }
  }
}}


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




*多次元配列 [#s627d3c3]

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

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS