C言語の基本|配列の宣言と初期化

配列を作るだけで終わらせず、最初の値まできちんと決める。宣言と初期化を覚えて、安全で読みやすい配列を用意しよう。

配列を使うときは、最初にどの型の値を何個保存するのかを決めます。この保存場所を用意する操作が宣言です。

ただし、関数内でローカル配列を宣言しただけでは、各要素に意味のある値が入っているとは限りません。初期化していないローカル配列の要素は不定値になるため、そのまま計算や表示に使ってはいけません。

そこで大切になるのが初期化です。

初期化を使うと、配列を宣言するのと同時に最初の値を設定できます。すべての要素へ値を入れるだけでなく、次のような書き方も可能です。

  • 初期化子の個数から要素数を決める
  • 一部の要素だけに値を指定する
  • 残りの要素を自動的に0にする
  • 配列全体を0で初期化する
  • 要素指示子で特定の位置から初期化する

配列を用意した直後の状態を明確にしておくと、未設定の値を誤って使う危険が減り、コードの意図も伝わりやすくなります。

宣言と初期化の違い

宣言と初期化は、似ているようで役割が異なります。

操作意味
宣言配列の型、名前、要素数を決めて保存場所を用意する
初期化配列を宣言すると同時に最初の値を設定する

次のコードは配列の宣言です。

int data[5];

int型の要素を5個持つ配列dataを用意していますが、関数内でこのように宣言しただけでは、各要素は初期化されていません。

次のコードは、宣言と同時に初期化しています。

int data[5] = {10, 20, 30, 40, 50};

配列を用意すると同時に、5個の要素へ値を設定しています。

図1:宣言だけの配列と初期化した配列

この図から分かること

宣言だけを行ったローカル配列には、使用できる値が設定されていません。

一方、初期化した配列では、宣言直後から各要素の値が明確になっています。

コード宣言直後の状態
int data[5];各要素は不定値
int data[5] = {10, 20, 30, 40, 50};すべての要素に値が入っている
int data[5] = {0};すべての要素が0

配列をすぐに計算や表示へ使う場合は、宣言と同時に適切な値で初期化すると安全です。

1次元配列を宣言する文法

1次元配列は、次の形で宣言します。

型名 配列名[要素数];

それぞれの部分には、次の意味があります。

項目内容
型名配列の各要素が持つ型
配列名配列に付ける名前
要素数配列が持つ要素の個数

整数を5個保存する配列なら、次のように宣言します。

int scores[5];

この配列が持つ要素は次の5個です。

scores[0]
scores[1]
scores[2]
scores[3]
scores[4]

要素数は5ですが、添字は0から始まるため、最後の添字は4です。

宣言したローカル配列は不定値になる

関数内で次のように宣言した配列を考えてみましょう。

int scores[5];

初期化していないため、各要素の状態は次のようになります。

要素状態
scores[0]不定値
scores[1]不定値
scores[2]不定値
scores[3]不定値
scores[4]不定値

不定値は、0とは限りません。以前使われていた領域の内容などが残っている可能性があり、どのような値として見えるかは決まっていません。

そのため、次のように未初期化の要素を表示してはいけません。

int scores[5];

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

配列を参照する前に、初期化または代入によって使用できる値を設定しましょう。

1次元配列を初期化する文法

1次元配列を初期化するときは、次の形で記述します。

型名 配列名[要素数] = {初期値1, 初期値2, 初期値3, ...};

波括弧の中に並べた値を初期化子と呼びます。

部分内容
型名配列要素の型
配列名配列に付ける名前
要素数配列が持つ要素の個数
=初期値を与える
波括弧内の値各要素へ設定する初期化子

たとえば、5日分の生産数を保存するなら、次のように書けます。

int production[5] = {120, 145, 98, 160, 132};

初期化子は先頭から順番に入る

初期化子は、配列の先頭から順番に対応します。

int production[5] = {120, 145, 98, 160, 132};

初期化後の状態は次のとおりです。

添字要素
0production[0]120
1production[1]145
2production[2]98
3production[3]160
4production[4]132

波括弧の1番目の値は添字0、2番目の値は添字1へ入ります。

初期化子の並びと添字の対応を意識すると、どの要素にどの値が入るのか読み取りやすくなります。

要素数と初期化子の数が同じ場合

もっとも基本的なのは、要素数と初期化子の数を同じにする書き方です。

int temperatures[5] = {-18, -20, -19, -17, -21};

要素数は5、初期化子も5個なので、すべての要素へ値が入ります。

要素
temperatures[0]-18
temperatures[1]-20
temperatures[2]-19
temperatures[3]-17
temperatures[4]-21

どの要素にも初期値が明示されているため、学習の最初にも分かりやすい形です。

初期化子があれば要素数を省略できる

宣言と同時に初期化する場合は、角括弧内の要素数を省略できます。

int temperatures[] = {-18, -20, -19, -17, -21};

初期化子が5個あるため、コンパイラは要素数を5と判断します。

次の2つは、どちらも要素数5の配列になります。

int temperatures[5] = {-18, -20, -19, -17, -21};
int temperatures[]  = {-18, -20, -19, -17, -21};

要素数を省略する書き方には、初期値を追加または削除したときに要素数を数え直さなくてもよいという利点があります。

要素数を省略できない場合

初期化子がない場合は、要素数を省略できません。

次の宣言は正しい書き方です。

int numbers[3];

一方、次の書き方では配列が何個の要素を持つのか判断できません。

int numbers[];

要素数を省略できるのは、初期化子から配列の大きさを判断できる場合です。

書き方使用できるか理由
int a[3];使用できる要素数が指定されている
int a[] = {1, 2, 3};使用できる初期化子から要素数が分かる
int a[];使用できない要素数を判断できない

初期化子が要素数より少ない場合

初期化子は、要素数より少なくても構いません。

int data[5] = {10, 20, 30};

先頭から3個の要素に値が入り、指定されなかった残りの要素は0で初期化されます。

要素
data[0]10
data[1]20
data[2]30
data[3]0
data[4]0

宣言だけの場合は不定値ですが、初期化子を1つでも指定した配列では、指定されなかった残りの要素が0になります。

図2:不足した初期化子を0で補う仕組み

この図から分かること

初期化子が要素数より少ない場合、書かれていない後ろの要素は自動的に0になります。

int data[5] = {10, 20, 30};

この場合、data[3]とdata[4]は不定値ではなく0です。

宣言だけの場合との違いを整理すると、次のようになります。

宣言後ろの未指定要素
int data[5];不定値
int data[5] = {10, 20, 30};0
int data[5] = {0};すべて0

波括弧による初期化を行った場合は、明示されていない要素も初期化されます。

配列全体を0で初期化する

初期化子が足りない要素は0になる性質を使うと、配列全体を簡単に0で初期化できます。

int counters[5] = {0};

最初の要素には明示した0が入り、残りの要素も自動的に0になります。

要素
counters[0]0
counters[1]0
counters[2]0
counters[3]0
counters[4]0

次のようにすべての0を書く必要はありません。

int counters[5] = {0, 0, 0, 0, 0};

どちらも同じ結果ですが、int counters[5] = {0};のほうが短く、配列全体を0にする意図が伝わりやすい書き方です。

初期化子が多すぎる場合

初期化子の数は、指定した要素数を超えてはいけません。

次の配列の要素数は3ですが、初期化子は4個あります。

int values[3] = {10, 20, 30, 40};

配列にはvalues[0]、values[1]、values[2]の3要素しかありません。40を入れる要素がないため、この書き方は正しくありません。

基本的には、次の関係を守ります。

初期化子の数 <= 要素数

要素数を省略した場合は、初期化子の個数に合わせて配列の要素数が決まります。

int values[] = {10, 20, 30, 40};

この場合は要素数4の配列になるため、正しい初期化です。

要素指示子を使った初期化

C99以降では、要素指示子を使って特定の添字を指定できます。

int scores[5] = {[2] = 78, 69};

[2] = 78によって、scores[2]へ78を設定します。その次の69は、直前に指定した位置の次となるscores[3]へ入ります。

初期化後の状態は次のとおりです。

要素
scores[0]0
scores[1]0
scores[2]78
scores[3]69
scores[4]0

要素指示子で指定されていない要素は0で初期化されます。

要素指示子の読み方

次の初期化を左から順番に見てみましょう。

int scores[5] = {[2] = 78, 69};

処理の対応は次のようになります。

  1. 要素数5の配列scoresを用意する
  2. [2] = 78によってscores[2]へ78を設定する
  3. 次の初期化子69をscores[3]へ設定する
  4. 指定されていないscores[0]、scores[1]、scores[4]を0にする

要素指示子は、配列の途中にだけ意味のある初期値を置きたい場合に便利です。

初期化は宣言時に行う

波括弧を使った初期化は、配列を宣言するときに行います。

int data[3] = {10, 20, 30};

宣言後に、次のような形で配列全体へまとめて値を代入することはできません。

int data[3];

/* 配列全体への代入としては使えない */
data = {10, 20, 30};

宣言後に値を変更するときは、各要素を指定して代入します。

data[0] = 10;
data[1] = 20;
data[2] = 30;

初期化と代入は、値を入れるタイミングが違います。

操作タイミング
初期化配列の宣言時int data[3] = {10, 20, 30};
代入配列の宣言後data[0] = 10;

代表的な初期化方法をプログラムで確認する

ここでは、5台分のセンサー基準値を使って、代表的な初期化方法を確認します。

5種類の配列初期化を比較するプログラム

ファイル名:10_3_1.c

#include <stdio.h>

int main(void)
{
    /* 要素数と同じ数の初期化子を指定する */
    int levels1[5] = {12, 15, 18, 21, 24};

    /* 初期化子の個数から要素数を決める */
    int levels2[] = {9, 11, 13, 15, 17};

    /* 足りない要素は0で初期化される */
    int levels3[5] = {6, 12, 18};

    /* 配列全体を0で初期化する */
    int levels4[5] = {0};

    /* 要素指示子で特定の位置から初期化する */
    int levels5[5] = {[2] = 27, 30};

    printf("levels1: %d %d %d %d %d\n",
           levels1[0], levels1[1], levels1[2],
           levels1[3], levels1[4]);

    printf("levels2: %d %d %d %d %d\n",
           levels2[0], levels2[1], levels2[2],
           levels2[3], levels2[4]);

    printf("levels3: %d %d %d %d %d\n",
           levels3[0], levels3[1], levels3[2],
           levels3[3], levels3[4]);

    printf("levels4: %d %d %d %d %d\n",
           levels4[0], levels4[1], levels4[2],
           levels4[3], levels4[4]);

    printf("levels5: %d %d %d %d %d\n",
           levels5[0], levels5[1], levels5[2],
           levels5[3], levels5[4]);

    return 0;
}
実行結果の例
levels1: 12 15 18 21 24
levels2: 9 11 13 15 17
levels3: 6 12 18 0 0
levels4: 0 0 0 0 0
levels5: 0 0 27 30 0

levels1の初期化

levels1は、要素数と同じ数の初期化子を指定しています。

int levels1[5] = {12, 15, 18, 21, 24};

要素数は5、初期化子も5個です。書かれた順番に、添字0から4まで値が入ります。

要素
levels1[0]12
levels1[1]15
levels1[2]18
levels1[3]21
levels1[4]24

すべての値と対応する要素が明示されている、基本的な初期化です。

levels2の初期化

levels2では、角括弧内の要素数を省略しています。

int levels2[] = {9, 11, 13, 15, 17};

初期化子が5個あるため、コンパイラが要素数を5と判断します。

次の書き方と同じ要素数になります。

int levels2[5] = {9, 11, 13, 15, 17};

初期値の追加や削除が考えられる場合は、要素数を省略すると個数の書き間違いを防ぎやすくなります。

levels3の初期化

levels3は要素数5ですが、初期化子は3個です。

int levels3[5] = {6, 12, 18};

先頭から3個の要素には6、12、18が入り、残りの2個には0が入ります。

6 12 18 0 0

初期化子が不足しても、後ろの要素が不定値になるわけではありません。初期化を行った配列では、不足分が0になります。

levels4の初期化

levels4では、初期化子として0を1つだけ指定しています。

int levels4[5] = {0};

levels4[0]へ0が入り、残りの要素も不足分として0で初期化されます。

0 0 0 0 0

配列全体を0にしたい場合によく使われる書き方です。

levels5の初期化

levels5では、C99以降で利用できる要素指示子を使っています。

int levels5[5] = {[2] = 27, 30};

最初の初期化子では、添字2を明示しています。

[2] = 27

そのため、levels5[2]へ27が入ります。続く30は、その次のlevels5[3]へ入ります。

指定されていない要素は0になるため、結果は次のとおりです。

0 0 27 30 0

図3:要素指示子による特定位置の初期化

この図から分かること

要素指示子を使うと、先頭からではなく、指定した添字から値を設定できます。

int levels5[5] = {[2] = 27, 30};

この初期化は、次のように読み取れます。

  • levels5[2]へ27を入れる
  • 次のlevels5[3]へ30を入れる
  • levels5[0]、levels5[1]、levels5[4]は0にする

要素指示子の後ろに添字を指定しない初期化子が続く場合は、指定した要素の次から順番に値が入ります。

初期化方法を表で整理する

代表的な書き方を整理すると、次のようになります。

書き方初期化後の状態
int a[5] = {1, 2, 3, 4, 5};すべての要素へ順番に値を入れる
int a[] = {1, 2, 3, 4, 5};初期化子から要素数5を決める
int a[5] = {1, 2, 3};1、2、3、0、0
int a[5] = {0};すべて0
int a[5] = {[2] = 7, 8};0、0、7、8、0

どの書き方でも、初期化後の各要素がどのような値になるのかを考えることが大切です。

宣言だけと初期化ありを使い分ける

配列へ後からすべての値を入力する場合は、先に宣言だけを行うこともあります。

int data[5];

ただし、入力や代入が終わるまでは要素を参照しないようにします。

最初から基準値が決まっている場合や、0から集計を始める場合は、宣言と同時に初期化すると分かりやすくなります。

int limits[] = {10, 20, 30, 40, 50};
int counters[5] = {0};
用途選びやすい書き方
後から全要素へ入力する要素数を指定して宣言する
最初の値が決まっている宣言と同時に初期化する
全要素を0から使う{0}で初期化する
一部の位置だけ値を持たせる要素指示子を使う

よくある間違い

配列の宣言と初期化では、次の点に注意しましょう。

間違い正しい考え方
宣言しただけで0になると思うローカル配列は不定値になる
初期化子が少ないと残りが不定値になると思う初期化した配列の不足分は0になる
初期化子を要素数より多く書く初期化子は要素数以下にする
初期化子なしで要素数を省略する要素数または初期化子が必要
宣言後に波括弧で配列全体へ代入する宣言後は要素ごとに代入する
要素指示子をすべてのC規格で使えると思う要素指示子はC99以降
要素数5なら添字5まで使えると思う使用できる添字は0~4

初期化コードを読むときの確認ポイント

配列の初期化を見つけたら、次の順番で確認すると分かりやすくなります。

  • 配列の型を確認する
  • 配列名を確認する
  • 要素数が指定されているか確認する
  • 初期化子が何個あるか確認する
  • 初期化子がどの添字へ入るか確認する
  • 足りない要素が0になることを確認する
  • 要素指示子が使われているか確認する
  • 初期化子が要素数を超えていないか確認する

配列の宣言は保存場所を用意する操作で、初期化はその保存場所へ最初の値を設定する操作です。配列を作った直後の状態を明確にしておけば、その後の計算や表示も安心して行えるようになります。