※ 筑波大学渡辺紀生作成の演習ページ(外部リンク)から一部修正・転載。
注意: 本日の「演習・課題」は、課題1,2がこのページの中ほどにあります。その後、「二次元配列」解説、練習があり、最後に課題3があります。課題を見落とさないように気をつけてください。
今回は、テキスト第5章に従って配列を学びます。一言で言えば、配列は、同じ型の変数の集まりです。配列は、同じ型の変数の集まりを「背番号」(※1)で管理します。この背番号は添え字と呼ばれる整数値です。科学計算などで多数のデータを扱う場合には、必ず配列を用いることになります。
※1) この背番号を「インデックス」といいます。
配列を使うためには、変数の時と同様に、まず「宣言」を行う必要があります。
たとえば、整数型の配列宣言は以下のように行います。
int a[5]; /* 領域は確保されるが値は不定 */
int a[5] = {1, 2, 3, 4, 5}; int a[5]= {0}; /* 全て0で初期化される */
int a[] = {1, 2, 3, 4, 5}; /* a[5]が確保され指定の数値で初期化される。 */
ここでは4種類の宣言方法が紹介されています。それぞれの方法で、宣言した直後の配列に格納されている値が異なります。このように、宣言直後の変数に特定の値を格納することを「初期化」といいます。それぞれの場合でどのように配列が初期化されるかはコードの中のコメントをみてみてください。
int 以外の型の配列も同様に宣言できます。たとえば、int の部分を double とすれば倍精度浮動小数点型の配列が確保されます。
宣言した後の配列に値を代入するには次のようにします。
a[3] = 5; /* 配列 a の「4番目」の要素に5を代入。 */
ここで、インデックスが "3" なのに、値を代入している先は"4番目の要素"で在る点に注意して下さい。これは、配列のインデックスが "0" から始まるためです。つまり、a[0] が1番目の要素、a[1]が2番めの要素、ということです。
これと関連して、配列のインデックスの最大値に注意してください。配列宣言で [] の中に入っている数字は準備する配列の「要素の数」です。たとえば、上の例のように
int a[5];
と配列宣言した場合に使えるのは a[0] から a[4] までであり、a[5] は確保されません。無理やりa[5]に値を代入しようとすると、実行中にプログラムが予期せぬ振舞をして、動作ミス、実行時エラーなどの原因になります。
まず、次のプログラム(テキスト p. 90, List 5-2)を見てみましょう。これは、配列の使い方を示したものです。すでに見たように、配列は、まず宣言して領域を確保する必要があります。繰り返しになりますが、int vc[5] と宣言すると確保される変数は vc[0] から vc[4] までであることに注意してください。配列の添字は 0 から始まります。
/* 配列の各要素に先頭から順に 1, 2, 3, 4, 5 を代入して表示 */
#include<studio.h>
int main(void) {
int vc[5]; /* 配列宣言 要素数が 5 の配列を宣言 */
vc[0] = 1; /* 配列への値の代入 */
vc[1] = 2;
vc[2] = 3;
vc[3] = 4;
vc[4] = 5;
printf("vc[0] = %d\n", vc[0]);
printf("vc[1] = %d\n", vc[1]);
printf("vc[2] = %d\n", vc[2]);
printf("vc[3] = %d\n", vc[3]);
printf("vc[4] = %d\n", vc[4]);
return (0);
}
すでに見たように、配列は、まず宣言して領域を確保する必要があります。繰り返しになりますが、int vc[5] と宣言すると確保される変数は vc[0] から vc[4] までであることに注意してください。配列の添字は 0 から始まります。
続く部分では配列の各要素に値を代入しています。
最後に、printf(...) の部分で、配列に保存されている値を読みだして表示しています。これをみるとわかるように、vc[4] のように[]で囲まれたインデックスをつければ通常の変数のように値の読み書きができる、ということです。
ここまでのように、配列の直接数字でインデックスしていたのでは通常の変数とあまりかわりません。配列の便利さは for ループと組み合わせることで発揮されます。次のプログラム(テキストの list 5-3)を見て下さい。
/* 配列の各要素に先頭から順に 1, 2, 3, 4, 5 を代入して表示(for 文) */
#include<studio.h>
int main(void) {
int i;
int vc[5]; /* 要素数が 5 の配列 */
for (i = 0; i < 5; i++)
vc[i] = i + 1;
for (i = 0; i < 5; i++)
printf("vc[%d] = %d\n", i, vc[i]);
return (0);
}
このプログラムでは for ループでループをカウントするための変数 i を使うことで、配列 vc の要素に順番にアクセスしています。
最初にすこし出てきた「初期化」の話にもどりましょう。配列を使用する前には、一般にそれぞれの要素に特定の値を代入して初期化する必要があります。これは次のように宣言と同時に行うことも出来ます。
/* 配列の各要素を先頭から順に 1, 2, 3, 4, 5 で初期化して表示 */
#include<stdio.h>
int main(void) {
int i;
int vc[5] = {1, 2, 3, 4}; /* 初期化 1 */
for (i = 0; i < 5; i++)
printf("vc[%d] = %d\n", i, vc[i]);
return (0);
}
配列は、同じ型・サイズの配列であっても代入演算子で一度に全ての要素を別の配列にコピーすることはできません。常にひとつの要素ごとのコピーとなります。以下の例を参考にしてください。
/* 配列の全要素を別の配列にコピー */
#include<stdio.h>
int main(void) {
int i;
int va[5] = {15, 20, 30}; /* {15,20,30,0,0} で初期化 */
int vb[5];
for (i = 0; i < 5; i++)
vb[i] = va[i];
puts(" va vb");
puts("-------");
for (i = 0; i < 5; i++)
printf("%3d%3d\n", va[i], vb[i]); return (0); }
次は全要素を逆順に並び替えるプログラムです(p.95, List 5-8)。
配列とfor文を組み合わせて使うと、大きなデータに同じ処理を繰り返す計算を簡潔に表現することができます。下のプログラムの配列の要素数を10個に変更してみよう。
/* 配列の全要素を逆順に並べかえる */ #include<stdio>
int main(void)
{ int i; int vx[5]; for (i = 0; i < 5; i++) { printf("vx[%d]:", i); scanf("%d", &vx[i]); } for (i = 0; i < 2; i++) { int temp = vx[i]; vx[i] = vx[4 - i]; vx[4 - i] = temp; } for (i = 0; i < 5; i++) printf("vx[%d]=%d\n", i, vx[i]); return (0); }
5人の学生の点数をキーボードから配列に読み込んで、その合計点と平均点、及び昇順に並び替えたそれぞれの点数を表示するプログラムを作成せよ。
出力例
5人の点数を入力
1人目:24
2人目:65
3人目:83
4人目:47
5人目:59
合計点= 278
平均点= 55.6
昇順に並び替えた点数
24
47
59
65
83
出力例
rand関数による100000個の乱数の生成
0 ~ 99: 300回
100 ~ 199: 287回
200 ~ 299: 277回
300 ~ 399: 306回
400 ~ 499: 292回
500 ~ 599: 294回
600 ~ 699: 325回
700 ~ 799: 305回
800 ~ 899: 301回
900 ~ 999: 305回
多次元配列とはインデックスが2つ以上ある配列のことです。インデックスが2つの多次元配列は、例えば、「行列」というイメージでとらえるとわかりやすいと思います。(前回やったインデックスが一つの配列は「ベクトル」というイメージですね。)
多次元配列の初期化方法は、1次元配列と同様の規則に従います。 たとえば、整数型の2次元配列宣言では次のように初期化できます。
int a[5][5]; /* 領域は確保されるが値は不定 */
int a[2][2] = {{1, 2}, {3, 4}};
int a[5][5]= {0}; /* 全て0で初期化される */
int a[][3] = {{1, 2, 3}, {4, 5,6}}; /* a[2][3]が確保され指定の数値で初期化される。*/ /* 初期化子が与えられている場合、最初の要素数を省略することが可能 */
という宣言方法があります。int の部分をdoubleとすれば倍精度浮動小数点型の配列が確保されます。
多次元配列の詳細はテキスト5-2を参考にしてください。
テキストList 5-14の行列を加算するプログラムを実行してみましょう。
/* 2行3列の行列を加算する */
#include<stdio.h>
int main(void) {
int i, j;
int ma[2][3] = { {1, 2, 3}, {4, 5, 6} };
int mb[2][3] = { {6, 3, 4}, {5, 1, 2} };
int mc[2][3] = { 0 };
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
mc[i][j] = ma[i][j] + mb[i][j];
for (i = 0; i < 2; i++) {
for (j = 0; j < 3; j++)
printf("%3d", mc[i][j]);
putchar('\n');
}
return (0);
}
配列ma, mbを初期化子を与えて初期化し、両者を加えた結果をmcに代入しています。配列mcも初期化子を与えて0で初期化していますが、これは特に必要ありません。
下の2つの行列 x, y の積を求めるプログラムを作成せよ。
ただし、それぞれの行列を次のように宣言して用いること。
int x[3][3] = { { 1, 2, 3}, { 4, 5, 6}, { 7 , 8 , 9}};
int y[3][3] = { { 1, 4, 7}, { 2, 5, 8}, { 3 , 6 , 9}};
結果は2行2列の行列となるが、結果を z[i][j] とすると、z[i][j] = x[i][0]*y[0][j] + x[i][1]*y[1][j] + x[i][2]*y[2][j] となる。
結果の表示は以下のようにすれば良い。
printf("*** 結果 ***\n");
printf(" %3d %3d %3d \n", z[0][0], z[0][1], z[0][2]);
printf(" %3d %3d %3d \n", z[1][0], z[1][1], z[1][2]);
printf(" %3d %3d %3d \n", z[2][0], z[2][1], z[2][2]);
printf("***********\n");