配列を使うと、連続したデータを扱う「特定の型の変数」の集合を一まとめにできます。配列は繰り返し文、その中でも特にfor文と非常に相性が良く、ソースコードをシンプルに記述できるようになります。たとえば5人の平均点を求めるプログラムは、下記のように記述できます。
int i;
int p[5];
int sum = 0;
int ave;
for (i = 0; i < 5; i++) {
sum += p[i];
}
ave = sum / 5;
5人程度であれば、べた書きしても大した負担にはならないかもしれません。しかし配列を使うことで10人でも100人でも同じコードで処理できるのが特徴です。
配列の宣言
配列は下記のように宣言します。
データ型 変数名[要素数];
要素数には1以上の整数を設定します。例えば要素数3のint型は下記のように宣言し、各要素には変数名に[ ]の添え字(番号)を付けてアクセスします。添え字は0から始まり、宣言時の「要素数-1」までとなります。
int a[3];
a[0] = 3;
a[1] = 6;
a[2] = 9;
- 配列の要素数以上の添え字を指定してはいけません。これはC言語でもっとも気を付けなければならない過ちの1つです。バッファーオーバーフローによりメモリー破壊が起こります。
上の例でいうと「a[3] = 12;」は厳禁です。
配列の初期化
スカラー変数を宣言と同時に初期化できるように、配列変数も宣言と同時に初期化できます。
// 1要素ずつ初期値を指定する
int a[3] = {3, 6, 9};
// すべてを0で初期化する場合のみ下記が可能
int a[3] = {0};
また宣言と同時に初期化する場合のみ、宣言時に要素数を省略することもできます。
// 要素数は3となる
int a[] = {3, 6, 9};
- 宣言時に要素数を省略することは推奨しません。要素数は明確にして宣言すべきです。
配列の要素数を得る
配列の要素数は後から調べることができます。このような方法を用いることは推奨はしませんが、手法として知っておいた方がいいので紹介しておきます。
// int型は4バイトとする
int a[3];
// 配列全体のサイズ = 4 * 3 = 12
size_all = sizeof(a);
// 配列の1要素のサイズ = 4
size_one = sizeof(a[0]);
// 配列の要素数 = 12 / 4 = 3
num = sizeof(a) / sizeof(a[0]);
- この手法が有効なのは、配列を宣言したブロック内のみです。ポインターに対してsizeof演算子を適用してはいけません。
配列を関数に渡す
配列も引数として関数に渡すことができます。この場合は変数の値を直接渡すのではなく、配列の先頭アドレスをポインター変数に渡すことで実現します。下記はその例です。
#define COUNT (3)
void func(void)
{
int a[COUNT] = {3, 6, 9};
int sum;
// sum = 3 + 6 + 9 = 18
sum = Calc_Sum(a, COUNT);
}
int Calc_Sum(int a[], int size) // "int a[]"は"int *a"と同意
{
int i;
int sum = 0;
for (i = 0; i < size; i++) {
sum += a[sum];
}
return sum;
}
- 添え字を省いた配列名は、配列の先頭アドレスと同意です。このため「&a[0] = a」となります。
また引数として渡すのはアドレスであることから、下記のようにすることで配列の要素の一部のみを関数に渡すこともできます。
#define COUNT (5)
void func(void)
{
int a[COUNT] = {3, 6, 9, 12, 15};
int sum;
// sum = 6 + 9 + 12 = 27
sum = Calc_Sum(&a[1], COUNT - 2);
}
- 配列を関数に渡す際は、配列のサイズも合わせて渡してください。
2次元配列
これまで紹介してきた配列は1次元の配列となります。これをさらに拡張して、2次元やそれ以上の多次元配列を作り出すこともできます。下記は2次元配列の例です。
int a[3][5];
a[0][0] = 3;
a[1][1] = 6;
a[2][2] = 9;
2次元配列を用いることで、縦×横のマス目状のデータを取り扱いやすくなります。上記例をわかりやすくすると、下記表のようになります。
x = 0 | x = 1 | x = 2 | x = 3 | x = 4 | |
---|---|---|---|---|---|
y = 0 | a[0][0] | a[0][1] | a[0][2] | a[0][3] | a[0][4] |
y = 1 | a[1][0] | a[1][1] | a[1][2] | a[1][3] | a[1][4] |
y = 2 | a[2][0] | a[2][1] | a[2][2] | a[2][3] | a[2][4] |
2次元配列の初期化
2次元配列変数も宣言と同時に初期化できます。
int a[3][5] = {
{1, 2, 3, 4, 5},
{2, 4, 6, 8, 10},
{3, 6, 9, 12, 15}
};
2次元配列を関数に渡す
C言語では、2次元配列をスマートに引数として関数に渡すことができません。下記例のとおり、要素数を省略できないのです。
#define X_SIZE (5)
#define Y_SIZE (3)
int Calc_Sum(int a[][X_SIZE], int y_size) {
int x, y;
int sum = 0;
for (y = 0; y < y_size; y++) {
for (x = 0; x < X_SIZE; x++) {
sum += a[y][x];
}
}
return sum;
}
void func(void) {
int a[Y_SIZE][X_SIZE] = {
{1, 2, 3, 4, 5},
{2, 4, 6, 8, 10},
{3, 6, 9, 12, 15}
};
int sum;
sum = Calc_Sum(a, X_SIZE);
}
引数の要素数が固定になってしまうと汎用性が下がってしまいます。これを回避する対策としていくつかあります。1つは考えられる最大サイズの配列を宣言し、その中で使用している範囲をするという下記のパターンです。ただしこれは明らかに無駄が多い手法です。
#define X_SIZE (640)
#define Y_SIZE (480)
int Calc_Sum(int a[Y_SIZE][X_SIZE], int x_size, int y_size) {
int x, y;
int sum = 0;
for (y = 0; y < y_size; y++) {
for (x = 0; x < x_size; x++) {
sum += a[y][x];
}
}
return sum;
}
2つ目は2次元配列を1次元の配列とみなす方法です。2次元配列の各要素は、下記表のとおりアドレス上では一列に並んでいます。
x = 0 | x = 1 | x = 2 | x = 3 | x = 4 | |
---|---|---|---|---|---|
y = 0 | 0 | 2 | 4 | 6 | 8 |
y = 1 | 10 | 12 | 14 | 16 | 18 |
y = 2 | 20 | 22 | 24 | 26 | 28 |
このことを利用して、下記例のように処理することができます。
#define X_SIZE (5)
#define Y_SIZE (3)
int Calc_Sum(int a[], int x_size, int y_size) {
int x, y;
int sum = 0;
for (y = 0; y < y_size; y++) {
for (x = 0; x < x_size; x++) {
sum += a[y * x_size + x];
}
}
return sum;
}
void func(void) {
int a[Y_SIZE][X_SIZE] = {
{1, 2, 3, 4, 5},
{2, 4, 6, 8, 10},
{3, 6, 9, 12, 15}
};
int sum;
sum = Calc_Sum(&a[0][0], X_SIZE, Y_SIZE);
}
- このことから言えるのは、C言語には2次元配列というものはないということです。コンパイラーが2次元配列のように扱ってくれているに過ぎません。