コンテンツにスキップ

C言語/配列

出典: フリー教科書『ウィキブックス(Wikibooks)』

配列の基本概念

[編集]

配列とは何か

[編集]

配列は、同じデータ型の複数の値を1つの変数名で扱うことができるデータ構造です。配列の各要素は番号付けされており、この番号をインデックスと呼びます。配列の各要素にはインデックスを使ってアクセスできます。

メモリ上の配置

[編集]

配列の要素は、メモリ上に連続して配置されています。つまり、1つの配列の各要素は、隣接する場所に格納されています。このため、配列の先頭アドレスと要素のサイズがわかれば、任意の要素のアドレスを計算できます。

配列と変数の違い

[編集]

変数は1つの値を格納するための記憶領域ですが、配列は複数の値を格納する記憶領域です。また、配列の要素は番号付けされているため、インデックスを使ってアクセスできます。

配列の宣言と初期化

[編集]

配列の宣言

[編集]

配列を宣言するには、データ型、配列名、配列のサイズを指定する必要があります。

データ型 配列名[サイズ];

例えば、要素数が5のint型の配列を宣言するには、以下のようにします。

int ary[5];

配列の初期化

[編集]

配列を初期化するには、要素に値を割り当てます。初期化子リストを使って、配列の宣言と同時に初期化することもできます。

データ型 配列名[] = {値1, 値2, 値3, ...};

例えば、要素数が5のint型の配列を初期化するには、以下のようにします。

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

配列の操作

[編集]

配列要素へのアクセス

[編集]

配列の要素にアクセスするには、配列名とインデックスを使います。インデックスは0から始まります。

配列名 [ インデックス ]

例えば、arr配列の3番目の要素にアクセスするには、以下のようにします。

ary[2] = 100; // ary[2]の値を100に設定

ループを使った配列操作

[編集]

配列の要素を処理するには、ループを使うのが一般的です。forループは配列操作に便利です。

for (int i = 0; i < sizeof 配列名 / sizeof *配列名; i++) {
    // 配列名[i]を使って各要素にアクセス
}

配列の使用例

[編集]

基本的な使用例

[編集]

配列は様々な用途で使用できます。代表的な使用例を以下に示します。

  • 複数の値を1つの変数で扱う
  • 関数の引数として複数の値を渡す
  • 関数の戻り値として複数の値を返す
  • データ構造の実装

多次元配列

[編集]

二次元配列の概念

[編集]

二次元配列は、配列の配列です。つまり、各要素が配列になっている配列のことです。二次元配列は、行と列のインデックスを使ってアクセスします。

二次元配列の宣言と初期化

[編集]

二次元配列を宣言するには、行と列のサイズを指定します。

データ型 配列名[行サイズ][列サイズ];

例えば、3行4列のint型の二次元配列を宣言するには、以下のようにします。

int ary[3][4];

初期化子リストを使って、二次元配列を初期化することもできます。

int ary[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

二次元配列の操作

[編集]

二次元配列の要素にアクセスするには、行と列のインデックスを使います。

ary[行インデックス][列インデックス]

例えば、arr配列の2行3列目の要素にアクセスするには、以下のようにします。

ary[1][2] = 100; // ary[1][2]の値を100に設定

配列とポインタ

[編集]

配列名とポインタの関係

[編集]

配列名は、実際には配列の先頭要素のアドレスを指すポインタ定数です。したがって、配列名と配列の先頭要素のアドレスは同じ値を持ちます。

ポインタを使った配列操作

[編集]

ポインタを使うと、配列の要素にアクセスできます。ポインタ演算子を使って、ポインタを配列の要素に合わせることができます。

*(arr + i) // ary[i]と同じ

文字列と文字配列

[編集]

文字列の概念

[編集]

C言語には文字列型は存在しませんが、null終端文字(\0)で終わる文字配列を文字列として扱うことができます。

文字配列の操作

[編集]

文字配列は通常の配列と同様に操作できますが、文字列関数を使うと便利です。string.hヘッダファイルに定義されている関数を使って、文字列の操作ができます。

配列の制約と注意点

[編集]

メモリ管理

[編集]

配列のサイズは、コンパイル時に決まります。つまり、実行時に配列のサイズを変更することはできません。動的にサイズを変更したい場合は、動的メモリ割り当てを使う必要があります。

可変長配列

[編集]

VLA(Variable Length Array)は、C99標準で導入された可変長配列で、配列のサイズを実行時に決定できるという点で、従来の配列とは異なります。

VLAについて補足すると、以下のような特徴があります。

  • 関数の中でのみ使用可能
  • 配列のサイズを実行時に決定できる
  • 自動変数として割り当てられ、関数終了時に自動的に解放される
  • 可読性が高く、動的メモリ割り当てに比べてコードが簡潔になる

例えば、以下のようにVLAを使うことができます。

#include <stdbool.h>
#include <stdio.h>

// エラトステネスのふるいを実行して、n未満の素数を表示する関数
void sieve_of_eratosthenes(int n) {
    if (n < 2) {
        printf("2未満の素数はありません。\n");
        return;
    }

    // VLAを使用して配列を宣言
    bool is_prime[n];
    for (int i = 0; i < n; i++) {
        is_prime[i] = true;
    }

    // エラトステネスのふるいアルゴリズム
    for (int p = 2; p * p < n; p++) {
        // is_prime[p]がtrueの場合は素数
        if (is_prime[p]) {
            // pの倍数を素数ではないとマーク
            for (int i = p * p; i < n; i += p) {
                is_prime[i] = false;
            }
        }
    }

    // 素数を表示
    printf("n未満の素数: ");
    for (int p = 2; p < n; p++) {
        if (is_prime[p]) {
            printf("%d ", p);
        }
    }
    printf("\n");
}

int main() {
    int n;
    printf("nの値を入力してください: ");
    scanf("%d", &n);
    sieve_of_eratosthenes(n);
    return 0;
}

このように、VLAを使えば実行時に配列のサイズを決めることができるので、より動的なプログラミングが可能になります。ただし、VLAにもいくつかの制限があり、関数の中でのみ使用可能で、大きなサイズを指定するとスタックオーバーフローの危険があることに注意が必要です。

境界チェックの重要性

[編集]

配列のインデックスが範囲外になると、プログラムの動作は未定義になります。したがって、配列操作では必ず境界チェックを行うことが重要です。

章末問題

[編集]

理解度チェック

[編集]
  1. 配列とは何か、その特徴を説明しなさい。
  2. 以下の配列宣言と初期化は正しいか。間違っている場合は理由を説明しなさい。
    int ary[5] = {1, 2, 3, 4, 5, 6};
  3. 以下のコードの出力は何か。
int ary[] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
    printf("%d ", *(arr + i));
}
  1. 3行4列の二次元配列を宣言し、初期化するコードを書きなさい。
  2. 文字列"Hello, World!"を格納する文字配列を宣言し、初期化するコードを書きなさい。