フリーキーズ | 独学プログラミング

C言語の配列を徹底解説!初心者でもわかるメモリ操作の基礎

リンドくん

リンドくん

たなべ先生、C言語の配列って何ですか?なんだか難しそうで...

たなべ

たなべ

配列はデータをまとめて扱うための仕組みなんだ。
例えばクラスの点数を管理するときに、一人ひとりの変数を別々に作るのではなく、まとめて扱えると便利だよね。

配列とは

プログラミングを学び始めると必ず出会う概念が「配列」です。
特にC言語では、配列の理解がプログラミングの基礎力を大きく左右します。

配列とは、同じ型のデータを連続したメモリ領域にまとめて格納する仕組みのことです。
例えば、100人分の点数データを管理する場合、score1, score2, score3...と100個の変数を作るのは非効率です。配列を使えばscore[0], score[1], score[2]...というように一つの変数名で管理できます。

C言語の配列がなぜ重要かというと、C言語はメモリを直接操作する言語だからです。
配列を理解することは、コンピュータのメモリ管理の基本を学ぶことにもつながります。これはPythonやJavaScriptなどの高級言語では隠蔽されている部分ですが、本格的なプログラマを目指すなら避けては通れません。

C言語の配列の基本的な使い方

リンドくん

リンドくん

配列ってどうやって作るんですか?
普通の変数と何が違うんでしょう?

たなべ

たなべ

配列は宣言するときに要素数(サイズ)を指定する必要があるんだ。
そして特徴的なのは、インデックス(添字)を使ってアクセスする点だよ。実際のコードで見てみよう!

配列の宣言と初期化

C言語で配列を使うには、まず宣言が必要です。
基本的な形式は以下の通りです。

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

具体的な例を見てみましょう。

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

配列を宣言すると同時に初期化することもできます。

int scores[5] = {80, 75, 90, 85, 95}; // 初期値を設定

すべての要素を同じ値で初期化したい場合は以下のようになります。

int scores[5] = {0}; // すべての要素を0で初期化

配列の要素へのアクセス

配列の各要素にアクセスするには、インデックス(添字)を使います。
C言語の配列は
0から始まる
ことに注意が必要です。

scores[0] = 80; // 1番目の要素に80を代入
scores[1] = 75; // 2番目の要素に75を代入
...
scores[4] = 95; // 5番目の要素に95を代入

printf("1番目の学生の点数: %d\n", scores[0]); // 80が表示される
printf("5番目の学生の点数: %d\n", scores[4]); // 95が表示される

配列と繰り返し処理

配列の真価は繰り返し処理と組み合わせたときに発揮されます。
例えば、全学生の平均点を計算する例はこちらです。

int scores[5] = {80, 75, 90, 85, 95};
int sum = 0;
float average;

// 合計を計算
for (int i = 0; i < 5; i++) {
    sum += scores[i];
}

// 平均を計算
average = (float)sum / 5;
printf("平均点: %.2f\n", average); // 85.00が表示される

このように配列を使うことで、同じ処理を効率的に繰り返すことができます。
これがループと配列を組み合わせる基本的なパターンです。

C言語特有の配列の注意点

リンドくん

リンドくん

PythonやJavaScriptとは何か違いがありますか?

たなべ

たなべ

大きな違いが2つあるよ。
C言語の配列はサイズが固定で、さらにバウンダリ(範囲、境界)チェックがないんだ。
これはつまり、範囲外にアクセスしても言語がエラーを出してくれないってことなんだよ。

配列のサイズは固定

C言語の配列は、宣言時に指定したサイズから変更できません。
これはPythonやJavaScriptの配列(リスト)とは大きく異なる点です。

int scores[5]; // 5要素の配列を宣言

// 以下はC言語では不可能
// scores.append(100);  // Pythonならできる
// scores.push(100);    // JavaScriptならできる

必要に応じてサイズを変更したい場合は、動的メモリ割り当て(malloccalloc)を使用するか、最初から大きめのサイズで宣言する必要があります。

範囲チェックがない

C言語では、配列のインデックスが有効範囲内にあるかどうか(バウンダリ)のチェックが自動的に行われません。
これは重大なバグや脆弱性の原因になることがあります。

int scores[5] = {80, 75, 90, 85, 95};

// 以下は文法的には正しいが、実行時に予期せぬ動作を引き起こす可能性がある
scores[5] = 100;  // 範囲外アクセス!
scores[-1] = 50;  // 範囲外アクセス!

これらのコードはコンパイルエラーにならないことに注意してください。
実行時に他のメモリ領域を上書きしてしまい、プログラムのクラッシュやセキュリティ問題を引き起こす可能性があります。

この特性は、C言語が「信頼できるプログラマ」を前提としている証拠でもあります。
そのため、常に有効なインデックスでアクセスするよう注意する必要があります。

多次元配列でデータを表現する

リンドくん

リンドくん

表みたいな二次元のデータも扱えるんですか?

たなべ

たなべ

もちろん!それが多次元配列だよ。
例えば、クラスごとの学生の点数を管理したいときにピッタリなんだ。イメージとしては「配列の配列」と考えるといいよ。

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

二次元配列は「表」のようなデータを表現するのに適しています。
例えば、3クラス各5人の点数を管理する場合はこのようになります。

int class_scores[3][5]; // 3行5列の二次元配列

// 初期化する場合
int class_scores[3][5] = {
    {85, 90, 75, 80, 95},  // 1組の点数
    {70, 85, 80, 90, 75},  // 2組の点数
    {90, 80, 85, 75, 95}   // 3組の点数
};

多次元配列へのアクセス

多次元配列の各要素にアクセスするには、複数のインデックスを指定します。

// 2組の3番目の学生の点数を取得
int score = class_scores[1][2]; // 80が入る

// 3組の1番目の学生の点数を変更
class_scores[2][0] = 95; // 90から95に変更

多次元配列の走査

二次元配列を処理するには、通常、ネストしたループを使用します。

// 各クラスの平均点を計算
float class_averages[3];

for (int i = 0; i < 3; i++) {  // クラスのループ
    int sum = 0;
    for (int j = 0; j < 5; j++) {  // 各クラスの学生のループ
        sum += class_scores[i][j];
    }
    class_averages[i] = (float)sum / 5;
    printf("%d組の平均点: %.2f\n", i+1, class_averages[i]);
}

このように、多次元配列を使うことで複雑なデータ構造も表現できます。
ゲーム開発では、マップデータやキャラクターの状態管理などにも活用されています。

配列とポインタの関係

リンドくん

リンドくん

配列ってポインタとも関係があるんですよね?

たなべ

たなべ

鋭いね!C言語では配列名は実はポインタとほぼ同じなんだ。
配列の名前は、その配列の先頭要素のアドレスを表しているんだよ。これがC言語の配列を理解する上で最も重要なポイントかもしれないね。

配列名はポインタ

C言語において、配列名は配列の先頭要素へのポインタとして機能します。
これは非常に重要な概念です。

int numbers[5] = {10, 20, 30, 40, 50};
int *ptr = numbers; // 配列名を直接ポインタに代入できる

printf("numbers[0] = %d, *ptr = %d\n", numbers[0], *ptr); // 両方とも10が表示される
printf("numbers[2] = %d, *(ptr+2) = %d\n", numbers[2], *(ptr+2)); // 両方とも30が表示される

この例では、numbersは配列の先頭要素(numbers[0])のアドレスを表しているため、ポインタ変数ptrに直接代入することができます。

ポインタ演算を使った配列要素へのアクセス

ポインタ演算を使って配列要素にアクセスすることもできます。

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

for (int i = 0; i < 5; i++) {
    printf("numbers[%d] = %d, *(numbers+%d) = %d\n", 
           i, numbers[i], i, *(numbers+i));
}

このコードでは、numbers[i]*(numbers+i)は全く同じ値を表します。
これはC言語が内部的に配列添字をポインタ演算に変換しているためです。

配列の引数渡し

関数に配列を渡す場合、実際には配列の先頭要素へのポインタが渡されます。

void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int numbers[5] = {10, 20, 30, 40, 50};
    printArray(numbers, 5);
    return 0;
}

上記のprintArray関数は、以下のように書くこともできます。

void printArray(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);  // または printf("%d ", *(arr+i));
    }
    printf("\n");
}

この関係性を理解することで、C言語でのメモリ操作の理解が深まり、より高度なプログラミングテクニックを習得できるようになります。

配列の活用例

リンドくん

リンドくん

具体的に配列はどんな場面で使うんですか?
実際のプログラムではどう活用されるんでしょう。

たなべ

たなべ

実践的な例をいくつか見てみよう!
配列はデータの集合を扱う場面で大活躍するんだ。例えば、成績管理や簡単なゲーム、データ分析など様々な場面で使われているよ。

例1)簡単な統計計算

学生の点数から最高点、最低点、平均点を計算する例です。

#include <stdio.h>

int main() {
    int scores[10] = {85, 92, 78, 65, 88, 72, 93, 60, 80, 75};
    int max = scores[0];
    int min = scores[0];
    int sum = 0;
    
    for (int i = 0; i < 10; i++) {
        if (scores[i] > max) {
            max = scores[i];
        }
        if (scores[i] < min) {
            min = scores[i];
        }
        sum += scores[i];
    }
    
    float average = (float)sum / 10;
    
    printf("最高点: %d\n", max);
    printf("最低点: %d\n", min);
    printf("平均点: %.2f\n", average);
    
    return 0;
}

例2)シンプルなゲームマップ

2次元配列を使った簡単な迷路ゲームの例です。

#include <stdio.h>

int main() {
    // 0:通路、1:壁、2:プレイヤー、3:ゴール
    int map[5][5] = {
        {1, 1, 1, 1, 1},
        {1, 2, 0, 0, 1},
        {1, 1, 1, 0, 1},
        {1, 3, 0, 0, 1},
        {1, 1, 1, 1, 1}
    };
    
    // マップの表示
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            switch (map[i][j]) {
                case 0: printf(" "); break; // 通路
                case 1: printf("■"); break; // 壁
                case 2: printf("P"); break; // プレイヤー
                case 3: printf("G"); break; // ゴール
            }
        }
        printf("\n");
    }
    
    return 0;
}

例3)単語の検索

配列を使って簡単な文字列処理を行う例です。

#include <stdio.h>
#include <string.h>

int main() {
    char words[5][20] = {
        "apple",
        "banana",
        "cherry",
        "date",
        "elderberry"
    };
    
    char search[20];
    printf("検索する単語を入力してください: ");
    scanf("%s", search);
    
    int found = 0;
    for (int i = 0; i < 5; i++) {
        if (strcmp(words[i], search) == 0) {
            printf("単語 '%s' は配列の %d 番目に見つかりました。\n", search, i);
            found = 1;
            break;
        }
    }
    
    if (!found) {
        printf("単語 '%s' は見つかりませんでした。\n", search);
    }
    
    return 0;
}

これらの例からわかるように、配列はデータを整理して扱うための強力な機能です。
実際のプロジェクトでは、これらの基本的な使い方を応用して、より複雑なデータ構造やアルゴリズムを実装していきます。

まとめ

リンドくん

リンドくん

なるほど!配列はデータをまとめて扱うためのとても便利な仕組みなんですね。

たなべ

たなべ

その通りだよ!配列はプログラミングの基礎中の基礎だ。
しっかりマスターして、これからのプログラミング学習に活かしていこう!

C言語の配列は、プログラミングの基礎を学ぶ上で非常に重要な概念です。
配列を理解することで、データをまとめて効率的に扱うことができるようになります。

配列の重要ポイントをまとめましょう。

  • 配列は同じ型のデータを連続したメモリ領域に格納する仕組み
  • インデックスは0から始まることに注意する
  • C言語の配列はサイズが固定で、実行時に変更できない
  • 範囲チェックがないため、プログラマ自身が正しいインデックスを使う責任がある
  • 配列名はポインタとして機能する
  • 多次元配列を使うことで、複雑なデータ構造も表現できる

配列の基本をマスターしたら、動的メモリ割り当てや構造体と組み合わせた使い方にも挑戦してみてください。
また、より高度なデータ構造(リンクリスト、木構造など)の理解にも役立ちます。

C言語の配列を学ぶことは、コンピュータのメモリ管理の基本を理解することにもつながり、他のプログラミング言語を学ぶ際にも役立つ知識となります。

関連するコンテンツ