配列

同じデータ型の変数をまとめて扱う

変数を使うと、値を一時的に保存しておくことができます。
変数はプログラミングには欠かせないものですが、数が多くなってくると管理が大変になってきます。
配列(Array)を使えば、同じような用途の変数をひとつにまとめることができます。

例えば以下のようなコードがあるとします。


#include <stdio.h>

int main()
{
    int kazu1, kazu2, kazu3;
    kazu1 = 10;
    kazu2 = 20;
    kazu3 = 30;

    printf("kazu1: %d\nkazu2: %d\nkazu3: %d",
        kazu1, kazu2, kazu3);

    getchar();
}
kazu1: 10
kazu2: 20
kazu3: 30

配列を使用しない場合、必要な数だけ変数を用意する必要があります。
三つくらいならば問題なくても、時には100や1000などの膨大な数の変数が必要になることがあります。
それをひとつひとつ宣言していては大変です。

配列を使うと、以下のように書けます。


#include <stdio.h>

int main()
{
    int arrKazu[3];
    arrKazu[0] = 10;
    arrKazu[1] = 20;
    arrKazu[2] = 30;

    printf("arrKazu[0]: %d\narrKazu[1]: %d\narrKazu[2]: %d",
        arrKazu[0], arrKazu[1], arrKazu[2]);

    getchar();
}
arrKazu[0]: 10
arrKazu[1]: 20
arrKazu[2]: 30

5行目で配列の使用を宣言しています。
配列は、同じデータ型を持つ変数をまとめて扱うことができます。

配列の宣言

配列の宣言は、変数と同じようにまず使用したいデータ型を書きます。
配列名は自由です。
(変数名と同じ制限があります)

変数との違いは、最後に[]記号を書き、その中に配列の要素数を指定することです。


int arr[3];

配列の要素数

配列の要素数とは、その配列に保存しておけるデータの数です。
配列に保存される各データは要素と言います。
int arr[3];と宣言した場合は、先頭から順に0番、1番、2番の3つの整数(int型)のデータを保存できます。

配列の要素番号は0から始まるという点に注意しましょう。
要素数に「3」を指定した場合、配列の最後の番号は要素数から-1した「2」となります。

配列の各要素へのアクセス

配列に値を代入したり、値を取り出すには配列の先頭からの番号を[]内の数字で指定します。
この記号を添字演算子(そえじえんざんし)といいます。

添字演算子の中に記述する数字を添え字(インデックス)と呼びます。

arr[1] = 20;ならば、2番目の要素に20を代入できます。
arr[0] = arr[1];ならば、先頭の要素に2番目の要素の値をコピーできます。


int arr[3];

arr[1] = 20;
arr[0] = arr[1]

以下のように配列自体に別の配列を代入することはできません。


int arr1[3];
int arr2[3];

//エラー  
arr1 = arr2;

配列全体のコピーをしたい場合は、各要素をひとつひとつ代入していく必要があります。


arr1[0] = arr2[0];
arr1[1] = arr2[1];
arr1[2] = arr2[2];

要素数が多くなるとひとつずつ書くのは大変ですが、これはループ文を用いれば簡単に記述できます。
(ループ文については別途説明します)

範囲外アクセス

配列の添え字に、マイナス値や「その配列の要素数-1」よりも大きな値を指定すると配列の範囲外へのアクセスが発生します。
これはどのような動作になるのかが保証されないので範囲外アクセスは行わないように注意してください。


int arr[3];

//範囲外アクセス!
arr[100] = 1;

//これも範囲外アクセス!
arr[3] = 1;
arr[-1] = 1;

配列の初期化あれこれ

配列は、変数の時と同じように宣言と同時に初期化することができます。


#include <stdio.h>

int main()
{
    int arr[3] = { 10, 20, 30 };

    printf("arr[0]: %d\narr[1]: %d\narr[2]: %d",
        arr[0], arr[1], arr[2]);

    getchar();
}
arr[0]: 10
arr[1]: 20
arr[2]: 30

要素数3の配列を宣言すると同時に、先頭から順に10、20、30の値で初期化しています。
配列の宣言に続き、波括弧で配列に代入したい値を記述します。
これを初期化子リストといいます。

この書き方ができるのは配列の宣言時のみです。
配列を宣言した後に値を代入する場合にはこの方法は使えません。


int arr[3];

//これはダメ
arr = { 10, 20, 30 };

//これもダメ
arr[3] = { 10, 20, 30 };

要素数を指定しないで初期化

配列の宣言と初期化を同時に行う場合は、要素数の指定を省略することもできます。


#include <stdio.h>

int main()
{
    int arr[] = { 10, 20, 30 };

    printf("arr[0]: %d\narr[1]: %d\narr[2]: %d",
        arr[0], arr[1], arr[2]);

    getchar();
}

このコードでは、初期化子リストの要素数から自動的に要素数3の配列を作成してくれます。

要素数を省略し、かつ初期化子リストも指定しない宣言はエラーになります。
配列は宣言時に要素数が確定できる必要があります。


//これはダメ
int arr[];

初期化子リストの要素数が配列の要素数より少ない場合

配列の宣言と初期化を同時に行う場合、指定した要素数よりも少ない要素数の初期化子リストで初期化することができます。
この時、初期化されずに残った要素は0で初期化されます。


#include <stdio.h>

int main()
{
    int arr[3] = { 10, 20 };

    printf("arr[0]: %d\narr[1]: %d\narr[2]: %d",
        arr[0], arr[1], arr[2]);

    getchar();
}
arr[0]: 10
arr[1]: 20
arr[2]: 0

要素数3の配列に対して、初期化子リストの要素はふたつしかありません。
この場合、先頭から値を埋めていき、余ったarr[2]は0で初期化されます。

これを利用すると、要素数が100だろうが1000だろうがすべてゼロで初期化してしまうことができます。
以下のように書くと、配列arrの100個の要素すべてに0で初期化された状態になります。


int arr[100] = {};

0で初期化したい場合は、必ず初期化子リストと共に配列を宣言してください。
初期化子リストなしで配列を宣言した場合、各要素の値は不定なため、要素に値を代入するまではその要素は使えません。
(不定値の使用は禁止)


int arr[100];

//一度も値が代入されていない要素の使用はNG
printf("%d", arr[0])

以下のコードは、1で初期化されるのは先頭要素のみで、後はすべて0で初期化されることに注意してください。


//先頭要素のみ1で初期化、
//後はすべて0で初期化
int arr[100] = { 1 };

配列でできないこと

配列の要素数の変更

C言語では、一度宣言した配列の要素数を変更することはできません。
(他の言語ではできるものもあります)

配列の要素数を変更したい場合は、新しい配列を作って元の配列の値をコピーする方法があります。


int arr1[3] = { 0, 1, 2 };
int arr2[5] = {}; //全ての要素を0で初期化

arr2[0] = arr1[0];
arr2[1] = arr1[1];
arr2[2] = arr1[2];

//arr2[3]とarr2[4]は0のまま

配列の要素数指定に変数を使用

配列の要素数の指定に変数を指定することはできません。


int kazu = 5;

//変数で配列の要素数を決定できない
//これはエラー
int arr1[kazu];

//確定した「数値」を指定しなければならない
//これはOK
int arr2[5];

配列はコンパイル時に要素数が確定できる必要があります。
変数のように、何が入っているかわからない値では配列の要素数を決定することはできません。
上のようなサンプルコードでは変数kazuの値は「5」であることは(人間にとっては)明らかなのですが、それでもできません。
配列の要素数は数値の直接指定のほか、「定数」という値が変わらないもののみを指定できます。

コンパイラによっては可変長配列という機能がサポートされている場合があり、この書き方が許可されている場合があります。
(Visual Studioではできない)

定数同士の計算はコンパイル時に値が決定されるので、要素数に指定することができます。
(小数型はNG)


//OK
int arr[10 / 2];

配列の要素数を計算で得る

配列の要素数は以下のような計算で算出することができます。


#include <stdio.h>

int main(void)
{
	int arr[] = { 0, 1, 2, 3, 4, 5 };

	int length = sizeof(arr) / sizeof(arr[0]);

	printf("配列arrの要素数: %d", length);

	getchar();
}
配列arrの要素数: 6

sizeofというのは演算子の一種で、指定したデータのサイズ(バイト数)を得ることができます。

sizeof演算子に配列を指定すると、その配列全体で使用されているデータサイズを得ることができます。
sizeof演算子に配列の要素を指定すると、要素ひとつ分のサイズを得ることができます。

配列全体のサイズを配列要素ひとつ分のサイズで割ると、配列の要素数が得られるというわけです。
int型の変数は32bit環境では4バイトのデータなので、上記コードは24 / 4 = 6の計算が行われ、要素数の6を得ることができます。
(データ型のサイズについては改めて説明します)