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


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

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

配列を宣言する

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

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

  型 配列名[要素数];

たとえば、要素数3のint型の配列 a を宣言すると、次のようになります。

  int a[3];

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

配列の要素にアクセスする

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

  int a[3];

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

  printf("%d\n", a[0]);

要素番号が 0 から始まることに注意しましょう。 したがって、最大の要素番号は要素数よりも1小さいです。

この配列は、実際のメモリーでは次のように並べて格納されます。

array.png

演習1

プログラム1を作成し、実行結果を確認せよ。

セグメント・エラー

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

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

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

  int a[3];

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

  printf("%d\n", a[10000]);

宣言した配列の要素数は3なので、要素番号の範囲は0から2までですが、このプログラムでは要素番号を10000にしています。

このプログラムを実行すると、次のようになります。

luna% ./a.out
セグメントエラー

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

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

次のプログラム(プログラム3)を実行すると、

  int a[3];
  int i = 12345;

  printf("%d\n", a[3]);

結果は次のようになります。

luna% a.out
12345

演習2

プログラム1をプログラム2、プログラム3に変更し、実行結果を確認せよ。

配列のすべての要素にfor文でアクセスする

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

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

  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 であることと繰り返し条件の不等号が <= ではなく < であることに注意しましょう。

演習2

プログラム1をプログラム4に変更し、実行結果を確認せよ。

配列を初期化する

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

配列を初期化するときは、波括弧 { } の中にコンマ , 区切りで要素を並べ、リストを作って代入します(プログラム5)。

  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);

配列を初期化するときに要素数を省略すると、初期化するリストの要素数が配列の要素数になります(プログラム6)。

  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);

配列を初期化するときのリストの要素数が宣言された配列の要素数よりも短いときは、残りの要素には 0 が代入されます(プログラム7)。

  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);

演習4

プログラム5-7を作成し、実行結果を確認せよ。

配列の要素数を調べる

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

sizeof演算子は、変数、配列、型の記憶容量(バイト数)を求める演算子です。

  int i;
  int a[3] = {250, 350, 500};

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

sizeofは関数のようにも見えますが、演算子です。

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

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

次のプログラムは、任意の長さの配列に対して、要素の合計を求めます(プログラム8)。

  int a[] = {250, 350, 500};

こうすると、配列を初期化するリストを変えても、その他の部分を変える必要がありません。

演習5

プログラム8を作成し、実行結果を確認せよ。

配列を関数の戻り値にすることはできない

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

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

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

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

次のプログラムの関数 sum は、int型の配列を引数として受け取って、その配列に含まれる要素の合計を返す関数です(プログラム9)。

  int i;
  int a[5] = {250, 350, 500};

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

演習6

プログラム9を作成し、実行結果を確認せよ。

配列を関数の引数にすると要素数は無視される

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

次のプログラムについて考えてみましょう(プログラム10)。

  sizeof(調べる対象)

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

このため、配列を関数の引数にするときは、要素数を指定する必要がありません(プログラム11)。

  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

プログラム10とプログラム11を作成し、実行結果を確認せよ。

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

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

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

そこで、任意の長さの配列に対応するために、引数に要素数を加えます(プログラム12)。

  int a[] = {250, 350, 500};
  int b[3];

  b = a;

演習8

プログラム11をプログラム12に変更し、実行結果を確認せよ。

2次元配列(配列の配列)

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

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

2次元配列を宣言するには、角括弧 [ ] と要素数の組をもう一つ追加します。

  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];
  }

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

たとえば、要素数3のint型の配列を4つ並べると、3行4列の2次元配列ができます。

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;
}

この2次元配列を図で表すと、次のようなイメージになります。

2d_array.png

実際のメモリーでは、2次元配列も(1次元の)配列と同じように順番に並べて格納されます。

2d_array_in_memory.png

行を優先して並べられていることに注意しましょう。

2次元配列を初期化する

2次元配列を初期化するときは、リストの中にリストを入れます。

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;
}

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

2次元配列のすべての要素にfor文でアクセスするときは、for文をネストします(入れ子構造にします)(プログラム13)。

int sum(int a[]) {
  int i, sum;

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

  return sum;  
}

演習9

プログラム13を作成し、実行結果を確認せよ。

多次元配列

2次元配列と同様にして、3次元以上の配列も作ることができます。

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;
}
トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS