閉じる

【C言語】配列についてわかりやすく解説 ~複数のデータをまとめて扱う基本テクニック~

C言語で複数のデータを扱うときに欠かせないのが配列です。

1つ1つの変数を個別に用意していると、コードが長くなりミスもしやすくなります。

そこで、同じ種類のデータをまとめて管理できる配列を使うことで、for文と組み合わせながら効率良く処理できるようになります。

この記事では、C言語初心者の方でも理解しやすいように、配列の基礎から実用的な使い方まで丁寧に解説していきます。

配列とは?複数データをまとめて扱う基本

配列とは何か

C言語の配列とは、同じ型のデータを、名前を1つにまとめたまとまりです。

たとえば、5人分のテスト点数を扱いたいとき、配列を使わない場合は次のように書くことになります。

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

このように変数を1つずつ用意すると、値を入力したり計算したりするコードも5回ずつ書く必要があり、とても非効率です。

そこで配列を使うと、次のように1つの名前で複数のデータを管理できます。

C言語
int score[5];   /* 5人分のint型の点数をまとめて管理する配列 */

このscoreという配列には、5つの要素があり、それぞれをscore[0]score[1]、…、score[4]のように区別して使います。

重要なポイントは「型は同じ」「数は固定」「連続したメモリに並ぶ」という3点です。

配列が便利な場面

配列が威力を発揮するのは、「同じような処理を、たくさんのデータに対して繰り返し行いたいとき」です。

具体的には次のような場面です。

  • 複数人のテスト点数を入力して、合計や平均を出したいとき
  • センサーの計測結果を一定回数分、順番に保存したいとき
  • 文字列(名前やメッセージなど)を扱いたいとき
  • グラフ描画用に、X座標やY座標の値を多数保存したいとき

これらはすべて、配列とfor文を組み合わせることで短く、読みやすく、間違えにくいコードにできます。

配列の宣言と要素数

配列を使うには、最初に宣言を行います。

配列の宣言は、次の形で書きます。

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

たとえば、int型の要素を10個持つ配列dataを宣言する場合は、次のように書きます。

C言語
int data[10];   /* int型の要素を10個持つ配列dataを宣言 */

ここで要素数とは、配列の中にいくつの値を保存できるかという数です。

上の例なら、data[0]からdata[9]まで、10個の要素を持つことになります。

注意したいのは、要素数は「変数ではなく定数(リテラル)」で書くのが基本という点です。

C言語の古い仕様では、要素数に変数を使うことはできません。

添字(インデックス)と配列要素の位置関係

配列の中の個々の値は、添字(インデックス)と呼ばれる番号で区別します。

配列名の後ろに[]を付けて、その中に番号を書く形です。

C言語
int a[3];   /* 要素数3の配列a */

/* 各要素への代入例 */
a[0] = 10;  /* 1番目の要素に10を代入 */
a[1] = 20;  /* 2番目の要素に20を代入 */
a[2] = 30;  /* 3番目の要素に30を代入 */

ここで重要なのは、配列の最初の要素は[0]から始まるという点です。

人間の感覚では「1番目」「2番目」と数えますが、C言語では0番目から数えるので、少し慣れが必要です。

配列のイメージを表にすると、次のようになります。

添字(index)要素の意味例(int a[3])
01番目の要素a[0]
12番目の要素a[1]
23番目の要素a[2]

0から始まる添字と要素数の関係に注意

配列の添字は0から始まり、最大の添字は「要素数 − 1」になります。

たとえば、要素数が5の配列なら、正しい添字の範囲は0 ~ 4です。

C言語
int b[5];   /* 要素数5の配列b */

/* 正しいアクセス範囲 */
b[0] = 1;
b[1] = 2;
b[2] = 3;
b[3] = 4;
b[4] = 5;

/* b[5]は存在しないので、これは誤り(範囲外アクセス) */

存在しない添字(たとえばb[5])にアクセスすると、メモリの不正な場所を使ってしまい、プログラムが異常終了したり、原因不明のバグが出るため、とても危険です。

この点は後半の「配列でつまずきやすいポイント」で詳しく説明します。

for文と配列の基本的な使い方入門

for文で配列を順番に処理する基本パターン

配列と最もよく組み合わせて使うのがfor文です。

for文を使うと、配列の全要素を先頭から末尾まで、順番に処理することができます。

基本パターンは次のようになります。

C言語
#include <stdio.h>

int main(void)
{
    int i;
    int a[5];

    /* 配列に値を代入する例 */
    for (i = 0; i < 5; i++) {
        a[i] = i * 10;  /* i番目の要素に i×10 を代入 */
    }

    /* 配列の中身を表示する例 */
    for (i = 0; i < 5; i++) {
        printf("a[%d] = %d\n", i, a[i]);
    }

    return 0;
}
実行結果
a[0] = 0
a[1] = 10
a[2] = 20
a[3] = 30
a[4] = 40

for文では、i = 0から始めて、i < 要素数という条件でループさせるのが基本形です。

こうすると、添字が0 ~ (要素数 - 1)の範囲でちょうど1回ずつ処理されます。

配列への入力(scanf)と出力(printf)の書き方

配列を使うと、複数の入力を連続して受け取ることも簡単になります。

for文とscanfを組み合わせると、次のように書けます。

C言語
#include <stdio.h>

int main(void)
{
    int i;
    int score[3];   /* 3人分の点数 */

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

    /* 配列への入力 */
    for (i = 0; i < 3; i++) {
        printf("%d人目の点数: ", i + 1);
        scanf("%d", &score[i]);   /* &score[i] に注意 */
    }

    printf("\n入力された点数を表示します。\n");

    /* 配列の出力 */
    for (i = 0; i < 3; i++) {
        printf("%d人目: %d点\n", i + 1, score[i]);
    }

    return 0;
}

配列の要素をscanfで読み込むときは、通常の変数と同様に&演算子を付けて&score[i]のように書きます。

合計値・平均値を計算するサンプルコード

配列を使うと、複数の数値の合計や平均を、短いコードで求められます。

次のサンプルでは、5人分の点数を入力し、合計と平均を求めます。

C言語
#include <stdio.h>

int main(void)
{
    int i;
    int score[5];
    int sum = 0;          /* 合計値 */
    double average = 0;   /* 平均値(小数も扱えるようにdouble型) */

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

    /* 入力と合計の計算を同時に行う例 */
    for (i = 0; i < 5; i++) {
        printf("%d人目の点数: ", i + 1);
        scanf("%d", &score[i]);

        sum += score[i];  /* 入力した点数をそのまま合計に加算 */
    }

    /* 平均値を計算(整数同士の割り算に注意) */
    average = (double)sum / 5.0;

    printf("\n合計点: %d\n", sum);
    printf("平均点: %.2f\n", average);

    return 0;
}

実行例の一例は次のようになります。

実行結果
5人分の点数を入力してください。
1人目の点数: 70
2人目の点数: 80
3人目の点数: 90
4人目の点数: 60
5人目の点数: 100

合計点: 400
平均点: 80.00

ここではsumをint型、averageをdouble型にして、(double)sum / 5.0のようにキャストしています。

こうすることで、小数点以下も含めた平均値を計算できます。

最大値・最小値を求めるシンプルな例

配列に入っている値の中から最大値最小値を探す処理も、for文と配列の代表的な応用です。

C言語
#include <stdio.h>

int main(void)
{
    int i;
    int score[5];
    int max, min;   /* 最大値と最小値 */

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

    /* まず最初の1人分を読み込む */
    printf("1人目の点数: ");
    scanf("%d", &score[0]);

    /* 最初の要素を、仮の最大値・最小値としておく */
    max = score[0];
    min = score[0];

    /* 2人目以降を処理 */
    for (i = 1; i < 5; i++) {
        printf("%d人目の点数: ", i + 1);
        scanf("%d", &score[i]);

        if (score[i] > max) {
            max = score[i];   /* より大きい値があれば更新 */
        }
        if (score[i] < min) {
            min = score[i];   /* より小さい値があれば更新 */
        }
    }

    printf("\n最高点: %d\n", max);
    printf("最低点: %d\n", min);

    return 0;
}

このように、最初の要素を基準値としておき、残りの要素と比較しながら更新していくのが、最大値・最小値を求める典型的な書き方です。

for文と配列で同じ処理をまとめて書くコツ

配列とfor文の組み合わせのポイントは、「同じパターンの処理を、添字だけ変えて繰り返す」ことです。

例えば、10個の要素をすべて0で初期化したい場合、1つずつ書くと次のようになり、とても大変です。

C言語
/* 悪い例(冗長) */
a[0] = 0;
a[1] = 0;
a[2] = 0;
/* ... */
a[9] = 0;

これをfor文で書き直すと、次のようにすっきりとしたコードになります。

C言語
int i;
int a[10];

/* すべての要素を0で初期化 */
for (i = 0; i < 10; i++) {
    a[i] = 0;
}

配列を使うときは、「この処理は全要素に共通だろうか?」と考え、共通部分をfor文にまとめるように意識すると、コードの質が大きく向上します。

int配列・char配列・float配列の書き方

int型の配列

最もよく使われるのがint型の配列です。

整数を扱う場合はほとんどがint型なので、自然とint配列を使う場面も多くなります。

C言語
#include <stdio.h>

int main(void)
{
    int i;
    int num[5] = {1, 2, 3, 4, 5};  /* 宣言と同時に初期化 */

    printf("int型配列numの要素を表示します。\n");

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

    return 0;
}

上の例では、{1, 2, 3, 4, 5}のように、波かっこで囲んだ値の並びを使って、配列を宣言と同時に初期化しています。

この書き方は、後ほど「配列の初期化」の項目でも詳しく説明します。

char型の配列と文字列の扱い方の違い

char型の配列は、文字を扱うときに使います。

ただし、C言語において文字列は、少し特殊なルールを持っています。

まず、1文字だけを扱う場合は、普通のchar変数を使います。

C言語
char c;
c = 'A';   /* 1文字は' 'で囲む */

一方、複数文字からなる「文字列」は、char型の配列を使って表現します。

たとえば、「CAT」という文字列を表現するには、次のような配列が内部で使われます。

添字0123
‘C’‘A’‘T’‘\0’

最後の'\0'ヌル文字と呼ばれ、文字列の終わりを示す特別な文字です。

C言語では、このヌル文字が現れるまでを1つの文字列として扱います。

このように、char配列と「C言語の文字列」は密接に結び付いている点を覚えておくと、後で文字列処理を学ぶときに理解しやすくなります。

文字列リテラルとchar配列の初期化方法

文字列を配列に保存するには、いくつかの書き方があります。

代表的なものを見てみましょう。

C言語
#include <stdio.h>

int main(void)
{
    /* 1: 1文字ずつ書く方法 */
    char str1[4] = {'C', 'A', 'T', '
#include <stdio.h>
int main(void)
{
/* 1: 1文字ずつ書く方法 */
char str1[4] = {'C', 'A', 'T', '\0'};
/* 2: 文字列リテラルで書く方法(おすすめ) */
char str2[4] = "CAT";
/* 3: 要素数を省略して、文字列リテラルに合わせる方法 */
char str3[] = "DOG";  /* 要素数は自動的に4になる(D O G \0) */
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf("str3 = %s\n", str3);
return 0;
}
'}; /* 2: 文字列リテラルで書く方法(おすすめ) */ char str2[4] = "CAT"; /* 3: 要素数を省略して、文字列リテラルに合わせる方法 */ char str3[] = "DOG"; /* 要素数は自動的に4になる(D O G
#include <stdio.h>
int main(void)
{
/* 1: 1文字ずつ書く方法 */
char str1[4] = {'C', 'A', 'T', '\0'};
/* 2: 文字列リテラルで書く方法(おすすめ) */
char str2[4] = "CAT";
/* 3: 要素数を省略して、文字列リテラルに合わせる方法 */
char str3[] = "DOG";  /* 要素数は自動的に4になる(D O G \0) */
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf("str3 = %s\n", str3);
return 0;
}
) */ printf("str1 = %s\n", str1); printf("str2 = %s\n", str2); printf("str3 = %s\n", str3); return 0; }

出力結果は次のようになります。

実行結果
str1 = CAT
str2 = CAT
str3 = DOG

文字列リテラルとは、"CAT""HELLO"のように" "で囲んだ文字列のことです。

これをchar配列に代入すると、自動的に末尾に'\0'が付きます。

要素数は「文字数 + 1」必要である点に注意してください。

例えば、”CAT”なら3文字なので、配列の要素数は少なくとも4必要です。

float,doubleの配列で小数を扱う方法

整数だけでなく、小数(実数)も配列で扱うことができます。

C言語では、主にfloat型とdouble型を使います。

C言語
#include <stdio.h>

int main(void)
{
    int i;
    float f[3] = {1.1f, 2.2f, 3.3f};   /* float型配列 */
    double d[3] = {1.11, 2.22, 3.33};  /* double型配列 */

    printf("float型配列fの要素:\n");
    for (i = 0; i < 3; i++) {
        printf("f[%d] = %.2f\n", i, f[i]);
    }

    printf("\ndouble型配列dの要素:\n");
    for (i = 0; i < 3; i++) {
        printf("d[%d] = %.2f\n", i, d[i]);
    }

    return 0;
}
実行結果
float型配列fの要素:
f[0] = 1.10
f[1] = 2.20
f[2] = 3.30

double型配列dの要素:
d[0] = 1.11
d[1] = 2.22
d[2] = 3.33

float型のリテラルには1.1fのようにfを付けるのが一般的です。

一方、double型は1.11のようにそのまま書けます。

型ごとのprintf,scanfのフォーマット指定子に注意

配列をprintfやscanfと組み合わせるときは、型に応じたフォーマット指定子を正しく使う必要があります。

配列であっても、各要素は単なる変数なので、指定子は通常の変数と同じです。

主な型と指定子の対応は次の通りです。

printfの指定子scanfの指定子
int%d%d
char%c%c
char配列(文字列)%s%s
float%f%f
double%f, %lf(*)%lf

※printfでは、double型でも%fを使うのが一般的です。

scanfではdouble型に%lfを使います。

例として、double型配列への入力・出力を示します。

C言語
#include <stdio.h>

int main(void)
{
    int i;
    double data[3];

    printf("double型の値を3つ入力してください。\n");

    for (i = 0; i < 3; i++) {
        printf("%d個目: ", i + 1);
        scanf("%lf", &data[i]);   /* double型なので%lf */
    }

    printf("\n入力された値:\n");
    for (i = 0; i < 3; i++) {
        printf("data[%d] = %.3f\n", i, data[i]);  /* 表示は%fでOK */
    }

    return 0;
}

配列でつまずきやすいポイントと注意点

添字の範囲外アクセスが危険な理由

配列で最も重大なミスが、添字の範囲外アクセスです。

存在しない添字にアクセスすると、C言語ではコンパイラがエラーを出さないことが多く、実行時に予測不能な挙動になります。

C言語
#include <stdio.h>

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

    /* 正しい範囲: a[0], a[1], a[2] */
    printf("a[0] = %d\n", a[0]);
    printf("a[1] = %d\n", a[1]);
    printf("a[2] = %d\n", a[2]);

    /* 間違い: 範囲外アクセス */
    printf("a[3] = %d\n", a[3]);  /* 要素は存在しない */

    return 0;
}

このプログラムはコンパイル自体は通ってしまうことがありますが、実行すると次のような問題が起こり得ます。

  • 変な値が表示される
  • たまたま他の変数の領域を読んでしまう
  • プログラムがクラッシュする

C言語では配列の範囲チェックを自動では行わないため、プログラマ自身が添字の範囲を常に意識して書く必要があります

要素数とループ条件をそろえる考え方

範囲外アクセスを避けるための最も基本的な対策は、「ループ条件を要素数ときちんと対応させる」ことです。

たとえば、要素数10の配列の場合、for文は次のように書きます。

C言語
int i;
int a[10];

for (i = 0; i < 10; i++) {
    /* a[i] は必ず有効な範囲(0~9) */
}

ここで間違いやすい書き方の例として、次のようなコードがあります。

C言語
for (i = 0; i <= 10; i++) {   /* NG: <= を使うと 0~10 の11回になる */
    /* a[i] を使うと、最後の i=10 のときに範囲外になる */
}

このようなバグを防ぐためのコツとして、「終了条件は < を使う」「i < 要素数 という形を徹底する」といったルールを自分の中で決めておくと良いです。

配列の初期化

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

代表的なものをまとめると次のようになります。

書き方説明
int a[3] = {1, 2, 3};すべての要素を指定して初期化
int a[5] = {1, 2};足りない要素は0で初期化
int a[5] = {0};すべての要素を0で初期化
int a[3];初期化なし(中身は不定)

実際のコード例を見てみましょう。

C言語
#include <stdio.h>

int main(void)
{
    int i;

    int x[3] = {1, 2, 3};   /* 1,2,3で初期化 */
    int y[5] = {1, 2};      /* 1,2,0,0,0で初期化 */
    int z[5] = {0};         /* すべて0で初期化 */

    printf("配列x:\n");
    for (i = 0; i < 3; i++) {
        printf("x[%d] = %d\n", i, x[i]);
    }

    printf("\n配列y:\n");
    for (i = 0; i < 5; i++) {
        printf("y[%d] = %d\n", i, y[i]);
    }

    printf("\n配列z:\n");
    for (i = 0; i < 5; i++) {
        printf("z[%d] = %d\n", i, z[i]);
    }

    return 0;
}

ポイントは{0}と書くと、すべての要素が0になる」ことです。

配列を1度0クリアしたいときに便利なテクニックです。

要素数を変更したときにバグを出さない工夫

プログラムを書いていると、後から配列の要素数を変えたくなることがあります。

たとえば、もともと10人分のデータ用にしていた配列を、あとで20人分に増やしたくなることがあります。

このとき、次のように「要素数の数字をあちこちで手書きしている」と、修正漏れからバグが発生しやすくなります。

C言語
/* 悪い例 */
int score[10];
for (i = 0; i < 10; i++) {
    /* ... */
}

この問題を避けるには、要素数を表す定数を定義しておき、それを使う方法が有効です。

C言語
#include <stdio.h>

#define STUDENT_NUM 10   /* 学生の人数を表す定数 */

int main(void)
{
    int i;
    int score[STUDENT_NUM];

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

    /* ここでも STUDENT_NUM を使用 */
    for (i = 0; i < STUDENT_NUM; i++) {
        printf("%d人目: %d点\n", i + 1, score[i]);
    }

    return 0;
}

要素数を20に増やしたくなったら、#define STUDENT_NUM 20の1か所を書き換えるだけで済みます。

配列とループ条件の整合性を自動的に保てるため、非常に重要なテクニックです。

配列とメモリ領域のイメージ

最後に、配列がメモリ上でどのように扱われているかを、イメージとして説明します。

C言語では、配列の要素はメモリ上で連続して並んでいます。

たとえば、次のような配列を考えます。

C言語
int a[4] = {10, 20, 30, 40};

メモリ上では、次のように並んでいるとイメージすると理解しやすくなります。

要素
a[0]10
a[1]20
a[2]30
a[3]40

実際には、それぞれの要素は4バイト(intのサイズ)ずつの領域を使っていて、それが連続しています。

この性質のおかげで、for文で添字を1つずつ増やしていくだけで、順番に要素をたどれるわけです。

また、文字列を表すchar配列も同様に、1文字ずつが連続して並んでおり、最後に'\0'が置かれています。

ポインタを学ぶ段階になると、配列名が配列の先頭要素へのポインタとして扱われることなども理解できるようになりますが、初心者の段階ではまず「連続した箱が並んでいる」というイメージを持つことが大切です。

まとめ

配列は、C言語で複数の同じ型のデータをまとめて扱うための、基本かつ強力な機能です。

添字は0から始まり、最大は要素数−1になること、for文と組み合わせて全要素を順番に処理できること、そして範囲外アクセスが非常に危険であることをしっかり押さえておきましょう。

int配列だけでなく、char配列による文字列やfloat・double配列など、さまざまな型で同じ考え方が使えます。

ここで紹介した基本パターンを自分の手で何度も書いてみることで、配列の感覚が自然と身に付き、今後のC言語学習の大きな土台になります。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

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

URLをコピーしました!