閉じる

【C言語】初心者向け配列入門|使い方から多次元配列まで徹底図解

C言語で最初につまずきやすいテーマの1つが配列です。

配列を理解すると、複数のデータを整理して扱えるようになり、プログラムの見通しが一気によくなります。

本記事では、「配列とは何か」から「多次元配列」「ポインタとの関係」まで、初心者の方にもわかりやすいように図解とサンプルコードを交えながら詳しく解説していきます。

配列の基礎知識

配列とは何か

配列は「同じ型の変数を、番号をつけて並べたもの」です。

1つ1つの値を個別の変数で管理する代わりに、1つの名前と番号(インデックス)でまとめて管理できるようにする仕組みです。

配列を使わない場合のイメージとして、テストの点数を5人分管理したいとき、次のように書く必要があります。

C言語
int score1;
int score2;
int score3;
int score4;
int score5;

配列を使うと、次のように1つのまとまりとして扱えます。

C言語
int scores[5];  /* scores という名前の配列を5個分確保 */

これにより、for文などを使って同じ処理をまとめて書けるようになり、コードが簡潔でミスも減らせます。

配列を導入すると、「同じ種類のデータをまとめて、規則的に処理できる」という大きなメリットが生まれます。

プログラムが少し長くなってきたら、配列を使うかどうかで読みやすさが大きく変わってきます。

配列の宣言と初期化の基本構文

配列を使うには、まず「宣言」します。

基本形は次のとおりです。

C言語
型名 配列名[要素数];

配列の宣言の例

C言語
int scores[5];      /* int型の要素を5個持つ配列 scores */
double data[10];    /* double型の要素を10個持つ配列 data */
char name[20];      /* char型(文字)を20個持つ配列 name */

ここでの要素数は「配列にいくつデータを入れられるか」を表しています。

配列の初期化の基本

宣言と同時に、配列に初期値を入れることもできます。

初期化の基本形は次のとおりです。

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

代表的な例をいくつか見てみます。

C言語
#include <stdio.h>

int main(void) {
    /* 整数型配列の初期化 */
    int scores[5] = { 80, 90, 75, 60, 100 };

    /* 要素数を省略した初期化(初期値の個数から自動計算される) */
    int nums[] = { 1, 2, 3, 4 };  /* 要素数は4になる */

    /* 0で埋めたいとき(一部だけ指定すると残りは0で初期化される) */
    int flags[5] = { 1 };         /* {1, 0, 0, 0, 0} となる */

    /* 文字の配列(後で詳しく説明します) */
    char hello[] = { 'H', 'e', 'l', 'l', 'o', '\0' };

    printf("%d\n", scores[0]);   /* 80 が出力される */
    printf("%d\n", nums[3]);     /* 4 が出力される */
    printf("%d\n", flags[2]);    /* 0 が出力される */
    printf("%s\n", hello);       /* Hello が出力される */

    return 0;
}
実行結果
80
4
0
Hello

要素数を省略して初期化する方法は、初期値の個数と要素数のズレによるミスを防ぐためにもよく使われます。

配列のインデックスと要素数の考え方

配列の中の1つ1つの値を要素と呼び、その位置を表す番号をインデックス(添え字)と呼びます。

C言語では、インデックスは必ず0から始まり、最後は(要素数 − 1)になります。

文章で表すと次のようになります。

  • 宣言int scores[5];なら、要素数は5です。
  • インデックスは0, 1, 2, 3, 4の5つになります。
  • scores[0]が最初の要素、scores[4]が最後の要素です。

インデックスと要素数の関係を整理すると、次の表のようになります。

要素数有効なインデックスの範囲最初の要素最後の要素
30, 1, 2a[0]a[2]
50, 1, 2, 3, 4a[0]a[4]
100〜9a[0]a[9]

インデックスが要素数以上になると範囲外アクセスとなり、バグやクラッシュの原因になります。

この点は後半で詳しく説明します。

配列の使い方

配列への値の代入と参照方法

配列を宣言したあとは、個々の要素に値を代入したり、取り出したりして使います。

基本は配列名[インデックス]という形です。

代入と参照の具体例

C言語
#include <stdio.h>

int main(void) {
    int scores[5];  /* 5人分の点数を入れる配列 */

    /* 要素への代入 */
    scores[0] = 80;
    scores[1] = 90;
    scores[2] = 75;
    scores[3] = 60;
    scores[4] = 100;

    /* 要素の参照(読み出し)と出力 */
    printf("1人目の点数: %d\n", scores[0]);
    printf("5人目の点数: %d\n", scores[4]);

    /* 計算に利用することもできる */
    int sum = scores[0] + scores[1];
    printf("1人目と2人目の合計: %d\n", sum);

    return 0;
}
実行結果
1人目の点数: 80
5人目の点数: 100
1人目と2人目の合計: 170

このように、普通の変数と同じように代入・参照ができますが、その前に必ず配列を宣言して要素数を確保しておく必要があります。

for文を使った配列の走査

配列の強みは、for文などと組み合わせて「まとめて処理」できることです。

配列の全要素を順番に処理することを「走査」と呼ぶことがあります。

for文で配列の中身を表示する例

C言語
#include <stdio.h>

int main(void) {
    int scores[5] = { 80, 90, 75, 60, 100 };
    int i;

    /* 配列の全要素を順番に出力する */
    for (i = 0; i < 5; i++) {
        /* i番目の要素を出力する */
        printf("%d人目の点数: %d\n", i + 1, scores[i]);
    }

    return 0;
}
実行結果
1人目の点数: 80
2人目の点数: 90
3人目の点数: 75
4人目の点数: 60
5人目の点数: 100

このとき、ループ条件はi < 要素数と書くのがポイントです。

うっかりi <= 5と書いてしまうと、scores[5]を参照しようとして範囲外アクセスになります。

文字列とchar配列の違いと使い分け

C言語では、文字列を「char型の配列」として扱います。

ただし、文字列には終端文字'\0'という特別な文字が必要です。

char配列としての文字と文字列

C言語
#include <stdio.h>

int main(void) {
    /* 1文字だけを入れる変数 */
    char c = 'A';

    /* 文字列(配列)としての "ABC" */
    char s1[4] = { 'A', 'B', 'C', '\0' };  /* 手動で終端文字を付ける */
    char s2[]  = "ABC";                    /* こちらの方が一般的 */

    printf("c: %c\n", c);
    printf("s1: %s\n", s1);
    printf("s2: %s\n", s2);

    return 0;
}
実行結果
c: A
s1: ABC
s2: ABC

ここで文字と文字列の違いを整理します。

種類型・定義の例中身のイメージ
文字char c = ‘A’;1文字だけ
文字列char s[] = “ABC”;‘A’,’B’,’C’,’\0′ の配列

文字列リテラル”ABC”は、内部的には{ 'A', 'B', 'C', '\0' }という配列として扱われます。

文字列を扱う配列の要素数は「文字数 + 1」(終端文字分)が必要という点に注意してください。

配列と標準入力・出力(scanf/printf)の扱い方

配列とscanfprintfを組み合わせると、複数の値をまとめて入力・出力できます。

整数配列を入力して平均を求める例

C言語
#include <stdio.h>

int main(void) {
    int scores[5];
    int i;
    int sum = 0;

    printf("5人分の点数を入力してください:\n");

    /* 配列に入力する */
    for (i = 0; i < 5; i++) {
        printf("%d人目: ", i + 1);
        /* &scores[i] は i番目の要素のアドレス */
        scanf("%d", &scores[i]);
    }

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

    printf("平均点: %.2f\n", (double)sum / 5);

    return 0;
}

この例では、入力するときの&scores[i]が重要です。

scanf「どこに入力結果を書き込むか」を知る必要があるため、変数や配列要素のアドレスを渡します。

文字列入力とchar配列

文字列を入力する場合も配列を使います。

代表的な方法としてscanfまたはfgetsがあります。

C言語
#include <stdio.h>

int main(void) {
    char name[20];  /* 最大19文字 + 終端文字のサイズ */

    printf("名前を入力してください: ");
    /* 空白を含まない単語として読み込む */
    scanf("%19s", name);
    /* %19s と書いておくことで、最大19文字までに制限(バッファあふれ防止) */

    printf("こんにちは、%sさん!\n", name);

    return 0;
}

ここでは文字列配列のときはscanf("%s", name);のようにnameだけを書き、&を付けない点が整数のときと異なります。

この理由は、後の「配列とポインタの関係」で説明します。

多次元配列の基本と活用

多次元配列とは

多次元配列は、配列の中にさらに配列があるような構造です。

最もよく使われるのは2次元配列で、「表」「マトリクス」「座標」などを表現するのに向いています。

2次元配列の宣言・初期化とアクセス方法

2次元配列の基本形は次の通りです。

C言語
型名 配列名[行数][列数];

2次元配列の宣言例

C言語
int matrix[2][3];  /* 2行3列のint型配列 */
int map[10][10];   /* 10行10列のマップ */

2次元配列の初期化

C言語
#include <stdio.h>

int main(void) {
    /* 2行3列の配列を初期化 */
    int matrix[2][3] = {
        { 1, 2, 3 },   /* 0行目 */
        { 4, 5, 6 }    /* 1行目 */
    };

    /* 波括弧をまとめて書くことも可能 */
    int matrix2[2][3] = { 1, 2, 3, 4, 5, 6 };

    printf("matrix[0][1] = %d\n", matrix[0][1]);  /* 2 */
    printf("matrix[1][2] = %d\n", matrix[1][2]);  /* 6 */

    printf("matrix2[0][2] = %d\n", matrix2[0][2]); /* 3 */
    printf("matrix2[1][0] = %d\n", matrix2[1][0]); /* 4 */

    return 0;
}
実行結果
matrix[0][1] = 2
matrix[1][2] = 6
matrix2[0][2] = 3
matrix2[1][0] = 4

2次元配列のインデックスの意味

2次元配列matrix[行][列]のインデックスは、次のように考えます。

  • 最初の番号: 行番号(上から0,1,2,…)
  • 2つ目の番号: 列番号(左から0,1,2,…)

多次元配列とネストしたfor文の書き方

2次元配列を扱うときは、for文を2重にネスト(入れ子)にして処理するのが定番です。

C言語
#include <stdio.h>

int main(void) {
    int matrix[2][3] = {
        { 1, 2, 3 },
        { 4, 5, 6 }
    };

    int i, j;

    /* 2次元配列の全要素を出力する */
    for (i = 0; i < 2; i++) {          /* 行を0〜1まで回す */
        for (j = 0; j < 3; j++) {      /* 列を0〜2まで回す */
            printf("%d ", matrix[i][j]);
        }
        printf("\n");                  /* 行の終わりで改行 */
    }

    return 0;
}
実行結果
1 2 3 
4 5 6

このように、外側のforが行、内側のforが列になるのが一般的です。

要素数を変えたときにミスしないために、行数・列数を定数マクロなどで定義しておくとより安全です(後ほど解説します)。

配列とポインタの関係

C言語では、配列とポインタは密接に関係しています。

配列名は「配列の先頭要素のアドレス」として振る舞うという特徴があります。

配列名と先頭要素のアドレス

C言語
#include <stdio.h>

int main(void) {
    int a[3] = { 10, 20, 30 };

    printf("a      = %p\n", (void*)a);      /* 配列aの先頭アドレス */
    printf("&a[0]  = %p\n", (void*)&a[0]);  /* 0番目の要素のアドレス */
    printf("&a[1]  = %p\n", (void*)&a[1]);  /* 1番目の要素のアドレス */

    return 0;
}

(出力例・アドレス値は実行環境により異なります)

実行結果
a      = 0x7ffee3b0c8c0
&a[0]  = 0x7ffee3b0c8c0
&a[1]  = 0x7ffee3b0c8c4

このように配列名a&a[0]とほぼ同じ意味として扱われます。

ただし、配列名自体に代入することはできないなど、完全に同じではない点もあります。

scanfでint配列とchar配列の扱いが違う理由

  • 整数配列のとき: scanf("%d", &a[i]); のように&を付ける
  • 文字列配列のとき: scanf("%s", s); のようにsだけでよい

これは、文字列配列sそのものが「先頭要素のアドレス」だからです。

整数の場合a[i]なので、アドレスを渡すには&a[i]と書く必要があります。

初心者がつまずきやすいポイントとコツ

配列の範囲外アクセスとバグの原因

配列で最も重大な問題が範囲外アクセスです。

例えばint a[5];に対してa[5]a[-1]にアクセスすると、プログラムは未定義の動作をします。

C言語では、範囲外アクセスを自動的に検出してエラーにしてくれません

そのため、自分でループ条件やインデックスを厳密に管理する必要があります

典型的なミスの例を挙げておきます。

C言語
int a[5];
int i;
for (i = 0; i <= 5; i++) {  /* <= 5 は間違い! */
    a[i] = i;
}

この場合、i = 5のときa[5]に書き込んでしまい、メモリ破壊の原因になります。

正しくはi < 5です。

配列の要素数管理と定数マクロの活用

要素数を直接数字で書いていると、後から要素数を変えたときに修正漏れが発生しやすくなります。

そこで、定数マクロを使って要素数を1カ所にまとめておくのが良い方法です。

C言語
#include <stdio.h>

#define STUDENT_NUM 5  /* 学生数を定数マクロで定義 */

int main(void) {
    int scores[STUDENT_NUM];
    int i, sum = 0;

    printf("%d人分の点数を入力してください:\n", STUDENT_NUM);

    for (i = 0; i < STUDENT_NUM; i++) {
        printf("%d人目: ", i + 1);
        scanf("%d", &scores[i]);
    }

    for (i = 0; i < STUDENT_NUM; i++) {
        sum += scores[i];
    }

    printf("平均点: %.2f\n", (double)sum / STUDENT_NUM);

    return 0;
}

このようにしておけば、学生数を10人に増やしたいときは#define STUDENT_NUM 10だけを修正すればよくループの上限や配列の宣言を個別に直してミスするといった事態を防げます。

2次元配列でも同じようにROWCOLといったマクロを用意して使うと、安全で読みやすいコードになります。

配列を関数に渡すときの注意点と書き方

配列を関数に渡すときも、いくつかの注意点があります。

Cでは配列をそのままコピーして渡すのではなく、先頭要素のアドレスを渡すという仕様になっています。

1次元配列を関数に渡す

C言語
#include <stdio.h>

#define N 5

/* 配列aの要素数n個を表示する関数 */
void print_array(int a[], int n) {
    int i;
    for (i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");
}

/* 上と同じ意味のプロトタイプ(書き換え可能) */
// void print_array(int *a, int n);

int main(void) {
    int data[N] = { 1, 2, 3, 4, 5 };

    print_array(data, N);

    return 0;
}
実行結果
1 2 3 4 5

ここでのポイントは次の通りです。

  • 関数定義のint a[]int *aとほぼ同じ意味(ポインタ)になります。
  • 関数内ではa[i]として通常どおりアクセスできます。
  • 配列の要素数は別の引数nで渡す必要があります。

2次元配列を関数に渡す際の注意

2次元配列を渡すときは列数を明示する必要があるため、少し書き方が変わります。

C言語
#include <stdio.h>

#define ROW 2
#define COL 3

/* 2次元配列の全要素を表示する関数 */
void print_matrix(int m[][COL], int row) {
    int i, j;
    for (i = 0; i < row; i++) {
        for (j = 0; j < COL; j++) {
            printf("%d ", m[i][j]);
        }
        printf("\n");
    }
}

int main(void) {
    int matrix[ROW][COL] = {
        { 1, 2, 3 },
        { 4, 5, 6 }
    };

    print_matrix(matrix, ROW);

    return 0;
}
実行結果
1 2 3 
4 5 6

関数側の宣言void print_matrix(int m[][COL], int row)のように、行数は空けてもよいが列数は固定して書く必要があります。

この点は初心者が混乱しやすいので、列数に対応するマクロを用意しておくと間違えにくくなります。

まとめ

配列は、C言語で複数の同じ型のデータを整理して扱うための基本的な仕組みです。

1次元配列ではインデックスと要素数の関係を正しく理解し、for文と組み合わせて安全に走査することが重要です。

文字列はchar配列として表現され、終端文字'\0'の存在がポイントになります。

さらに、2次元配列や配列とポインタの関係、関数への渡し方まで押さえることで、実用的なプログラムが書けるようになります。

範囲外アクセスを避けるためにも、定数マクロで要素数を一元管理し、常に「0から要素数−1まで」の意識を持って配列を扱うようにしてください。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!