[C言語のはじめ方] Part7: 1次元・2次元配列

C言語

1次元配列・2次元配列 🔢

こんにちは! C言語学習のStep 2へようこそ。🎉 ここでは、プログラムでたくさんのデータをまとめて扱うための重要な機能、「配列」について学んでいきます。Step 1で学んだ変数や制御構文の知識を活かしながら、さらに複雑なプログラムを作るための基礎を固めましょう。

💡 このセクションの目標

  • 配列とは何か、なぜ必要なのかを理解する。
  • 1次元配列の宣言、初期化、要素へのアクセス方法を習得する。
  • 2次元配列の宣言、初期化、要素へのアクセス方法を習得する。
  • 配列を使う上での注意点を理解する。

配列とは? 🤔

これまでは、int score = 85; のように、一つの変数に一つの値を入れてきましたね。でも、もしクラス全員(例えば30人)のテストの点数を扱いたい場合、score1, score2, …, score30 のように30個の変数を用意するのは大変です。😵

そこで登場するのが配列です。配列を使うと、同じ型の複数のデータを、一つの名前でまとめて管理できます。ちょうど、ロッカーが連なっているようなイメージです。各ロッカーには番号(インデックス)がついていて、その番号を指定することで中のデータを取り出したり、入れたりできます。

C言語の配列には、主に以下の特徴があります。

  • 同じデータ型の要素しか格納できない(例: int型の配列にはint型の値だけ)。
  • 一度作成した配列のサイズ(要素数)は変更できない(静的配列)。
  • 各要素には0から始まる連続した番号(インデックス、添え字)が割り当てられる。

1次元配列を使ってみよう!

まずは、シンプルな1次元配列から見ていきましょう。1次元配列は、データが一列に並んでいるイメージです。

1次元配列の宣言

配列を使うには、まず「こういう配列を使いますよ」と宣言する必要があります。宣言の仕方は次の通りです。

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

例えば、int型の値を5個格納できる配列 `scores` を宣言するには、以下のように書きます。

int scores[5]; // int型の値を5つ格納できる配列 scores を宣言

これで、`scores` という名前で、int型のデータを5個格納できる領域がメモリ上に確保されました。各要素には `scores[0]`, `scores[1]`, `scores[2]`, `scores[3]`, `scores[4]` という名前でアクセスできます。重要なのは、インデックスは0から始まるということです。要素数が5の場合、有効なインデックスは0から4までです。

⚠️ 注意点: インデックスの範囲

配列の要素数は5個なので、インデックスは 0 から 4 までです。scores[5]scores[-1] のように、範囲外のインデックスを使ってアクセスしようとすると、プログラムが予期せぬ動作をしたり、エラーで停止したりする原因になります(配列外アクセス)。これは非常に一般的なバグなので、常にインデックスの範囲を意識しましょう。

1次元配列の初期化

配列を宣言しただけでは、各要素には不定な値(ゴミデータ)が入っている可能性があります。配列を使う前に、意味のある値を入れてあげる(初期化する)ことが推奨されます。

初期化にはいくつかの方法があります。

1. 宣言と同時に初期化する

宣言時に `{}` を使って、各要素の値を指定できます。

int scores[5] = {85, 92, 78, 100, 88}; // 宣言と同時に初期化
// scores[0] = 85, scores[1] = 92, ..., scores[4] = 88 となる

初期化子の数が要素数より少ない場合、残りの要素は自動的に0で初期化されます(数値型の場合)。

int data[5] = {10, 20}; // data[0]=10, data[1]=20, data[2]=0, data[3]=0, data[4]=0 となる

全ての要素を0で初期化したい場合は、次のように書けます。

int zeros[5] = {0}; // 全要素が0で初期化される

また、初期化子を指定する場合、要素数を省略することもできます。この場合、コンパイラが初期化子の数から要素数を自動的に決定します。

int temps[] = {15, 18, 12}; // 要素数は3と自動的に決定される (temps[3])

2. 宣言後にループを使って初期化する

配列の宣言後に、`for`ループなどを使って各要素に値を代入することも一般的です。

#include <stdio.h>

int main(void) {
    int values[5]; // int型配列を宣言 (この時点では値は不定)
    int i;

    // forループを使って各要素に値を代入 (初期化)
    for (i = 0; i < 5; i++) {
        values[i] = i * 10; // values[0]=0, values[1]=10, ... values[4]=40
    }

    // 初期化された値を表示
    printf("初期化された配列要素:\n");
    for (i = 0; i < 5; i++) {
        printf("values[%d] = %d\n", i, values[i]);
    }

    return 0;
}
1次元配列の要素へのアクセス

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

配列名[インデックス]

例えば、`scores` 配列の3番目の要素(インデックスは2)にアクセスするには `scores[2]` と書きます。これは通常の変数と同じように、値の読み取りや代入が可能です。

#include <stdio.h>

int main(void) {
    int scores[5] = {85, 92, 78, 100, 88};
    int third_score;
    int sum = 0;
    int i;

    // 3番目の要素 (インデックス2) の値を取得
    third_score = scores[2];
    printf("3番目の点数: %d\n", third_score); // 出力: 3番目の点数: 78

    // 1番目の要素 (インデックス0) の値を変更
    printf("変更前の1番目の点数: %d\n", scores[0]); // 出力: 変更前の1番目の点数: 85
    scores[0] = 90;
    printf("変更後の1番目の点数: %d\n", scores[0]); // 出力: 変更後の1番目の点数: 90

    // forループを使って全要素の合計を計算
    for (i = 0; i < 5; i++) {
        sum += scores[i]; // sum = sum + scores[i]; と同じ
    }
    printf("合計点: %d\n", sum); // 出力: 合計点: 448 (90+92+78+100+88)

    // 平均点を計算 (キャストに注意)
    double average = (double)sum / 5;
    printf("平均点: %.2f\n", average); // 出力: 平均点: 89.60

    return 0;
}

2次元配列を使ってみよう!

次に、2次元配列について学びましょう。2次元配列は、データが表(テーブル)形式や格子状に並んでいるイメージです。例えば、数学の行列や、座席表などを表現するのに便利です。

2次元配列の宣言

2次元配列は、行と列の2つのインデックスを使って要素を指定します。宣言の仕方は次の通りです。

データ型 配列名[行数][列数];

例えば、3行4列のint型の2次元配列 `matrix` を宣言するには、以下のように書きます。

int matrix[3][4]; // 3行4列のint型2次元配列 matrix を宣言

これで、`matrix` という名前で、3行×4列 = 12個のint型データを格納できる領域が確保されました。各要素には `matrix[行インデックス][列インデックス]` という形でアクセスします。1次元配列と同様に、行インデックスも列インデックスも0から始まります

この例では、有効な行インデックスは 0 から 2 まで、有効な列インデックスは 0 から 3 までです。

  • matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3] (1行目)
  • matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3] (2行目)
  • matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3] (3行目)
2次元配列の初期化

2次元配列も、1次元配列と同様に初期化できます。

1. 宣言と同時に初期化する

波括弧 `{}` を入れ子にして、各行のデータを指定します。

// 3行4列の配列を初期化
int matrix[3][4] = {
    { 1,  2,  3,  4}, // 1行目 (matrix[0]) のデータ
    { 5,  6,  7,  8}, // 2行目 (matrix[1]) のデータ
    { 9, 10, 11, 12}  // 3行目 (matrix[2]) のデータ
};

内側の波括弧を省略して、全体を一つのリストとして書くことも可能ですが、行と列の対応が分かりにくくなるため、通常は上記のように入れ子にするのが推奨されます。

// 上記と同じ初期化 (分かりにくいので非推奨)
int matrix_alt[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

1次元配列と同様に、初期化子が要素数より少ない場合は残りが0で初期化され、行数を省略することも可能です(列数は省略できません)。

int data[][3] = { // 行数は初期化子から自動的に2と決定される
    {1, 1, 1},
    {2, 2, 2}
};

2. 宣言後にループを使って初期化する

2次元配列の場合、行と列に対応する2つのループ(二重ループ)を使うことが一般的です。

#include <stdio.h>

int main(void) {
    int table[3][5]; // 3行5列のint型配列
    int i, j;

    // 二重ループを使って初期化 (行番号 * 10 + 列番号)
    for (i = 0; i < 3; i++) {      // 行のループ (0から2まで)
        for (j = 0; j < 5; j++) {  // 列のループ (0から4まで)
            table[i][j] = i * 10 + j;
        }
    }

    // 初期化された値を表示
    printf("初期化された2次元配列:\n");
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 5; j++) {
            printf("%d\t", table[i][j]); // \t はタブ文字
        }
        printf("\n"); // 各行の表示後に改行
    }

    return 0;
}
2次元配列の要素へのアクセス

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

配列名[行インデックス][列インデックス]

例えば、`matrix[1][2]` は、2行目(インデックス1)の3列目(インデックス2)の要素を指します。

#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        { 1,  2,  3,  4},
        { 5,  6,  7,  8},
        { 9, 10, 11, 12}
    };
    int element;
    int sum = 0;
    int i, j;

    // 特定の要素を取得 (2行目3列目)
    element = matrix[1][2]; // 行インデックス1, 列インデックス2
    printf("matrix[1][2] の値: %d\n", element); // 出力: matrix[1][2] の値: 7

    // 要素の値を変更 (1行目1列目)
    matrix[0][0] = 99;
    printf("変更後の matrix[0][0] の値: %d\n", matrix[0][0]); // 出力: 変更後の matrix[0][0] の値: 99

    // 全要素の合計を計算
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            sum += matrix[i][j];
        }
    }
    printf("全要素の合計: %d\n", sum); // 出力: 全要素の合計: 175 (99+2+...+12)

    return 0;
}

💡 ちょっと補足: メモリ上の配置

2次元配列 `int matrix[3][4];` は、コンピュータのメモリ上では、実は1次元的に連続して配置されています。具体的には、「1行目の全要素、次に2行目の全要素、次に3行目の全要素」という順序(行優先順序、Row-major order)で並んでいます。つまり、メモリ上では matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3], matrix[1][0], matrix[1][1], ... , matrix[2][3] の順番でデータが格納されています。この知識は、後々ポインタと配列の関係を学ぶ際に役立ちます。

配列を使うメリットと注意点 ✅⚠️

メリット:

  • たくさんのデータを一つの名前で管理でき、コードがすっきりする。
  • ループ処理と組み合わせることで、大量のデータを効率的に扱える。
  • 関連性のあるデータをまとめて格納できる。

注意点:

  • 配列外アクセスをしないように、インデックスの範囲(0 から 要素数-1 まで)を常に意識する。
  • 宣言時に決めたサイズは後から変更できない(静的配列の場合)。
  • 同じデータ型の要素しか格納できない。

まとめ

今回は、C言語の基本的なデータ構造である「配列」について、1次元配列と2次元配列を中心に学びました。

  • 配列は、同じ型のデータを複数まとめて管理するための仕組み。
  • 要素へのアクセスには [インデックス] を使う。インデックスは0から始まる。
  • 1次元配列は データ型 配列名[要素数]; で宣言。
  • 2次元配列は データ型 配列名[行数][列数]; で宣言。
  • 宣言時の初期化や、ループを使った初期化・アクセスが一般的。
  • 配列外アクセスはバグの原因になるため、インデックスの範囲に注意が必要。

配列は、これからのプログラミングで頻繁に使うことになる非常に重要な概念です。色々なデータを使って配列を宣言したり、ループで操作したりする練習をしてみてくださいね。

次回は、処理をまとめて再利用可能にする「関数定義と呼び出し(引数・戻り値)」について学んでいきます。お楽しみに!🚀

コメント

タイトルとURLをコピーしました