パソナについて
記事検索

【ソースコード有】C言語の配列とは?初期化方法や使い方を紹介

今回は、C言語における配列の宣言と初期化のほか、代入やコピーなどの基本的な操作方法について説明します。

【ソースコード有】C言語の配列とは?初期化方法や使い方を紹介

今回は、C言語における配列の宣言と初期化のほか、代入やコピーなどの基本的な操作方法について説明します。

スキルアップ

2023/02/22 UP

「配列」は、同じ型の値をまとめて扱いたいときに便利なデータ構造の一つです。例えば、プログラム中でint型の値を100個使いたいときも、配列ならたった1行で宣言できます。活用のしかたを覚えれば、大量のデータを処理するようなプログラムも書けるようになるでしょう。

今回は、C言語における配列の宣言と初期化のほか、代入やコピーなどの基本的な操作方法について説明します。併せて2次元データや文字列としての扱い方、要素数の取得方法のような少し高度な話題にも触れていくので、ぜひ活用してみてください。

配列とは

C言語の配列は、同じ型のデータをひとまとめにして操作しやすくするためのものです。

まずは、どういうときに配列が必要になるのかを理解しましょう。次の例では、配列を使わずにint型の変数を3つ宣言しています。

/* 配列を使わない書き方 */
int number1;
int number2;
int number3;

この書き方では、扱いたいデータの個数だけ変数を用意しなければなりません。配列を使えば、次のように短く書くことができます。

/* 配列を使った書き方 */
int numbers[3];

配列によって、3つのデータがまとめられている様子がわかるでしょうか。この方法なら、データの個数が増えても変数を追加する必要がないため、大量のデータを扱うプログラムも書きやすくなります。

配列の宣言例

配列を宣言するには、次のように書くのが基本です。

データ型 配列名[要素数];

この宣言には、以下の3点が含まれています。

・データ型:配列の要素となる各データの型

・配列名:配列を識別するための名前

・要素数:配列で扱う要素の個数

下記は、データ型や要素数を指定して配列を宣言している例です。

/* 配列の宣言 */
int integerNumbers[10];
float floatingPointNumbers[20];
char characterNumbers[30];

この例では配列の宣言のみが行なわれていますが、宣言と同時に各要素の値を初期化する書き方もあります。

データ型 配列名[要素数(省略可)] = {1つ目の初期値, 2つ目の初期値, ……};

この書き方をするときは、要素数は省略しても構いません。配列に含めたい要素の数だけ初期値を指定すれば、その個数が要素数となるためです。例えば、10個の要素からなるint型の配列は、次のように記述できます。

/* 配列を宣言と同時に初期化(要素数は省略) */
int integerNumbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

2次元配列とは

C言語の配列では、その要素もまた配列にできます。これにより、多次元の配列を実現可能です。

例えば、行と列をもつ表形式のデータ構造は、次のように2次元の配列として宣言できます。

/* 2次元配列の宣言 */
int table[4][10];

要素数が2つ指定されているのがわかるでしょうか。これは、「4行×10列」のデータを格納できる2次元配列の例です。int型の要素10個でできた配列が、さらに4つ集まってできています。

2次元配列も、宣言と同時に要素を初期化することが可能です。

/* 2次元配列の初期化 */
int table[][10] = {
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
};

ここでは、1つ目の要素数が省略されていることに注目してください。C言語の配列を宣言と同時に初期化するときは、最初の要素数に限り省略できます。

配列の初期化や使い方

ここからは配列のより高度な初期化方法と、ポインタやコピー操作、文字列としての扱い方などについて説明していきます。大量のデータを効率良く処理するために必要となるテクニックです。

初期化の方法

C言語では、グローバル変数やstatic変数は自動的に0で初期化されます。これに対して、staticではないローカル変数は、明示的に初期値を与えなければ初期化されません。このルールは、配列にも適用されます。

int globalNumbers[100]; /* すべての要素が0で初期化される */

int main(void) {
    static int staticNumbers[100]; /* すべての要素が0で初期化される */
    int localNumbers[100]; /* 初期化されない */

    return 0;
}

そのため、main関数の内側などで用いるローカルな配列は初期化する必要があるのですが、要素数が多いと初期値をすべて書き出すのは大変な作業になるでしょう。そのような場合に役立つ効率的な初期化方法を2つ紹介します。

1つ目は、すべての要素を0にする方法です。

int localNumbers[100] = {0}; /* すべての要素が0で初期化される */

初期値の個数が1個なのに対し、要素数が100となっているのがわかるでしょうか。このような書き方をすると、不足している99個分の初期値にはすべて0が指定されたものとみなされます。最初の要素を明示的に0にしつつ、残りの要素も暗黙的に0で初期化しているというわけです。

しかし、0以外の値で初期化したいときは、上記の方法は使えません。その場合は、プログラムで初期化する2つ目の方法を使いましょう。

配列の各要素には、次の書式で値を設定できます。

配列名[インデックス] = 値;

インデックス(「添え字」ともいいます)は、何番目の要素なのかを示す0からはじまる数値です。次の例のようにfor文と組み合わせれば、配列の各要素を任意の値で初期化できます。

int localNumbers[100];
for (int i=0; i<100; i++) {
    localNumbers[i] = 1 + i; /* 各要素を任意の値で初期化する */
}

配列のポインタとアドレスアクセス

配列の要素は、メモリ内の隣接する領域にまとまって配置されます。インデックスの小さい要素から順番に、0番目、1番目、2番目……と一列に並んでいるイメージです。これは、配列の先頭がメモリ内のどこにあるのかさえわかれば、残りの要素にも素早くアクセスできることを意味しています。

この性質を利用して配列をより効果的に扱えるようになるために、ここで2つの用語を覚えましょう。

・アドレス:メモリ内に配置されているデータの位置を表す数値

・ポインタ:アドレスを格納できる変数

配列のアドレスは、配列名で参照できます。そのため、配列を指すポインタは以下のように宣言することが可能です。

int numbers[10];
int *numberPointer = numbers; /* 配列を指すポインタ */

ポインタで配列を操作するには、次のような「*(アスタリスク)」による書式を使います。


*(ポインタ + インデックス) = 値;

配列と同様に、インデックスを指定して任意の要素にアクセスできることがわかるでしょう。例えば、ポインタを用いて配列を初期化したい場合は、次のようにします。

int numbers[10];
int *numberPointer = numbers;
for (int i=0; i<10; i++) {
    *(numberPointer + i) = 1 + i;
}

配列をコピーする

配列ではないint型などの値は、次のようにすればコピーを作れます。

int num = 123;
int copyOfNum = num;

では、配列をコピーするには、どうすればよいでしょうか。

ポインタを使えば、コピーしたように見えるかもしれません。

int numbers[10];
int *numberPointer = numbers;

しかし、ポインタはメモリ内で配列と同じ位置を指しています。つまり、コピーされているのはアドレスだけで、配列の実体をコピーしているわけではありません。このようなコピーは、「浅いコピー」と呼ばれています。

配列の実体をコピーするには、次のように要素を1つずつ代入していく方法が簡単でしょう。このようなコピーは、「深いコピー」と呼ばれます。

int numbers[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int copyOfNumbers[10];
for (int i=0; i<10; i++) {
    copyOfNumbers[i] = numbers[i];
}

深いコピーは、メモリ内のデータを別の場所にコピーするmemcpy()関数で行なうことも可能です。memcpy()関数にはコピー先とコピー元のアドレス、およびコピーするデータのサイズを指定します。

サンプルコード:

#include <memory.h>
#include <stdio.h>

int main(void) {
    int numbers[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int copyOfNumbers[10];
    memcpy(copyOfNumbers, numbers, sizeof(numbers));

    for (int i=0; i<10; i++) {
        printf("コピーされた%d番目の値は%dです。\n", i, copyOfNumbers[i]);
    }

    return 0;
}

実行結果:

コピーされた0番目の値は1です。
コピーされた1番目の値は2です。
コピーされた2番目の値は3です。
コピーされた3番目の値は4です。
コピーされた4番目の値は5です。
コピーされた5番目の値は6です。
コピーされた6番目の値は7です。
コピーされた7番目の値は8です。
コピーされた8番目の値は9です。
コピーされた9番目の値は10です。

配列で文字列を宣言・定義する

C言語における文字列は、char型の配列の一種です。そのため、文字列は次のように配列に格納して使うこともできます。

char text[] = "sample";
printf("%s\n", text);

これは、次のように各要素を初期化するのと同じです。

char text[] = {'s', 'a', 'm', 'p', 'l', 'e', '\0'};
printf("%s\n", text);

ここでは、'\0'という特別な文字(「null文字」などと呼ばれます)が追加されている点に留意しましょう。C言語では、この文字で文字列の終端を識別します。

なお、文字列を変更する予定がない場合は、配列の代わりにポインタを使って次のように書いても構いません。

const char *text = "sample";
printf("%s\n", text);

こうすると、変更禁止のメモリ領域に文字列の実体を配置したうえで、そのアドレスがポインタに格納されます。constは、書き込み禁止を指定するためのキーワードです。

いずれの方法もC言語のプログラミングでは良く使われるので、しっかりと理解しておきましょう。

配列の要素数を取得する

C言語には、配列の要素数を取得するための関数が用意されていません。例えば次のような配列があるとき、要素がいくつ格納されているかは自分で計算しなければならないのです。

int numbers[] = {3, 14, 159, 2, 653, 5, 897, 93, 23, 8, 46, 264, 33, 83, 27, 950};

配列の要素数を調べるには、下記の計算式を利用できます。

(要素数)=(配列全体のサイズ)÷(要素1つ分のサイズ)

この計算が成り立つのは、配列の要素がメモリ内で隙間なく一列に並んでいるためです。配列全体や要素のサイズを取得するには、以下のようにsizeof演算子を使用します。

サンプルコード:

#include <stdio.h>

int main(void) {
    int numbers[] = {3, 14, 159, 2, 653, 5, 897, 93, 23, 8, 46, 264, 33, 83, 27, 950};
    printf("配列の要素数は%luです。\n", sizeof(numbers) / sizeof(numbers[0]));

    return 0;
}

実行結果:

配列の要素数は16です。

配列でまとめることで、一度にたくさんのデータが扱えるようになる

C言語のプログラム中で配列を用いると、同じ型のデータをまとめて格納できます。個々の要素にはインデックスで簡単にアクセスできるため、2次元の表や大量のデータを扱いたいときに便利です。

配列は柔軟な初期化が可能で、ポインタで操作したり、全体をコピーしたりといった使い方もできます。文字列を配列に格納する方法や、サイズから要素数を計算するテクニックなども、覚えておけば役立つ場面があるでしょう。