この記事は、C言語ドリルの4本目として配列を練習する記事です。複数のデータをひとつの変数にまとめるコードの書き方を身に着けます。

配列の宣言・添字から2次元配列までを7問の練習問題(基礎5問+発展2問)で手を動かして固めるドリルです。解答と解説は各問題の「+」を押すと開きます。まず自分で書いてみて、それから答え合わせをしてください。実務では、配列は「動作の履歴(ログ)を持つ」使い方が定番で、この記事でもそこまで踏み込みます。

<自己紹介>
筆者は現役の組込みエンジニアです。未経験向けのC言語練習問題を通じて最低限の言語知識を身につけ、最終的にはArduinoでマイコンプログラムを自作できるようになるのをゴールとして、このシリーズを作成しています。

このテーマの要点

配列でやることはこれだけ

  • 同じ型のデータをまとめて1つの名前で持つ(int data[5];
  • 1つずつは添字(index)で指す。先頭は0番
  • for と組み合わせて、全部を順に処理する

配列は前回のループとセットで使うことが多いです。「5教科の点数」「センサー値の履歴」「受信したデータの並び」など、数がまとまったデータは配列で扱います。

前提知識:配列の作り方・添字・2次元

① 作り方と初期化

変数の宣言との違いは、要素数(格納するデータの数)を決めることだけです。

配列

int data[5] = {10, 20, 30, 40, 50};
// 5個の整数を1つの名前 data で持つ

data[0] = 99;   // 読むだけでなく、書き換えもできる

② 添字(index)は0から始まる

添字

data[0]  // 先頭 → 10
data  // 2番目 → 20
data  // 最後(5個なら4番) → 50
先頭は「0番」。ここが最大の落とし穴 配列の添字は0から始まります。5個の配列なら、使える番号は 0〜4 で、data[5]存在しません。最大の添字は「宣言時の要素数 − 1」です。うっかり data[5] に触ると、他のデータを壊す危険なバグ(配列外アクセス)になります。
配列がメモリ上に一直線に並ぶ図。添字は0から始まる
図:int data[5] のメモリ上の並び。名前は1つでも、中身は連続した5つの箱

③ for で全部を順に処理する

走査

for (int i = 0; i < 5; i++) {
    printf("%d ", data[i]);   // data[0]〜data
}

④ 2次元配列(表の形でデータを持つ)

ここまでの配列はデータが1列に並ぶ「1次元」でしたが、行×列の表の形でも持てます。それが2次元配列です。

2次元配列

int a = {
    {0, 1, 2, 3},    // 1行目
    {4, 5, 6, 7}     // 2行目
};
// a[行][列] で指す。a は 6

イメージは表計算ソフトのセルです。a なら2行×4列=8個のデータが入ります。取り出すときは a[行][列] と添字を2つ書きます。ちなみに3次元(a)も作れて、xyz座標のようなデータで使われますが、4次元以降はほぼ使いません。まずは2次元まで知っていれば十分です。

より正確な仕様は、cppreference(C言語リファレンス)で確認できます。

例題(まずは一緒に解いてみる)

練習問題に入る前に、簡単な例題を1つ、答えを見ながら流れを確認しましょう。

例題:配列 {10, 20, 30} を作り、for で全部を順に表示してみましょう。

example.c

#include <stdio.h>

int main(void) {
    int data = {10, 20, 30};
    for (int i = 0; i < 3; i++) {
        printf("%d ", data[i]);
    }
    printf("\n");
    return 0;
}

実行結果:10 20 30

解説:data[i]i0→2 と動かして、全要素を取り出しています。添字は0からなので、3個の配列は data[0]〜data。条件を i < 3 にするのがポイントです。

流れはつかめましたか? ここからは、自分の手で解いてみましょう。

練習問題(基礎5+発展2)

基礎5問は必ず解いてください。発展2問は余力があればで大丈夫です。まず自分で書いてから、各問題の「+ 解答・解説を見る」を開いて答え合わせをしましょう。

基礎 Q1

5教科の成績(国語60・数学50・英語40・理科30・社会20)を1つの配列に格納し、国語:60 数学:50 英語:40 理科:30 社会:20 のように1行で表示してください。

解答・解説を見る
q1.c

#include <stdio.h>

int main(void) {
    int score[5] = {60, 50, 40, 30, 20};
    printf("国語:%d 数学:%d 英語:%d 理科:%d 社会:%d\n",
           score[0], score, score, score, score);
    return 0;
}

実行結果:国語:60 数学:50 英語:40 理科:30 社会:20

解説:5教科ぶんの変数を5個作る代わりに、1つの配列にまとめて添字で取り出すのが配列の基本です。score[0] が国語、score が社会。教科が10個に増えても、配列の要素数を増やすだけで管理できます。「番号と中身の対応」を自分で決めるのがコツです。

基礎 Q2

Q1の成績 {60, 50, 40, 30, 20}合計と平均を、for を使って求めて表示してください。

解答・解説を見る
q2.c

#include <stdio.h>

int main(void) {
    int score[5] = {60, 50, 40, 30, 20};
    int sum = 0;
    for (int i = 0; i < 5; i++) {
        sum += score[i];
    }
    printf("sum=%d avg=%d\n", sum, sum / 5);
    return 0;
}

実行結果:sum=200 avg=40

解説:sum に1つずつ足していけば合計が出ます。Q1のように添字を5回書かずに、forで i を回すのが配列×ループの基本形です。平均は sum / 5。ここは整数どうしの割り算なので小数は切り捨てられます(今回は200÷5=40でちょうど割り切れます)。小数の平均がほしいときはキャストを思い出してください。

基礎 Q3

配列 {12, 45, 7, 33, 20} の中から、一番大きい値を探して表示してください。

解答・解説を見る
q3.c

#include <stdio.h>

int main(void) {
    int data[5] = {12, 45, 7, 33, 20};
    int max = data[0];              // まず先頭を仮の最大に
    for (int i = 1; i < 5; i++) {
        if (data[i] > max) {
            max = data[i];          // より大きければ更新
        }
    }
    printf("max=%d\n", max);
    return 0;
}

実行結果:max=45

解説:最大値探しの定石は「先頭を仮の最大とし、より大きいものが出たら入れ替える」です。i を1から回すのは、先頭を max の初期値にしたぶん、比較は2番目からで足りるからです。最小値なら不等号を < に変えるだけです。

基礎 Q4

配列 {10, 20, 30, 40, 50}逆順(50から10へ)に表示してください。ヒント:for のカウンタは減らすこともできます。

解答・解説を見る
q4.c

#include <stdio.h>

int main(void) {
    int data[5] = {10, 20, 30, 40, 50};
    for (int i = 4; i >= 0; i--) {
        printf("%d ", data[i]);
    }
    printf("\n");
    return 0;
}

実行結果:50 40 30 20 10

解説:「最後の添字(要素数−1=4)から始めて、0まで i-- で減らす」のが逆順の定石です。開始は i = 5 ではなく i = 4。ここを間違えると存在しない data[5] に触ってしまいます。「最大の添字は要素数−1」を体で覚える問題です。

基礎 Q5

次の表のデータを2次元配列に格納し、同じ2行×4列の形で表示してください。

0 1 2 3
4 5 6 7
解答・解説を見る
q5.c

#include <stdio.h>

int main(void) {
    int a = {
        {0, 1, 2, 3},
        {4, 5, 6, 7}
    };
    for (int row = 0; row < 2; row++) {
        for (int col = 0; col < 4; col++) {
            printf("%d ", a[row][col]);
        }
        printf("\n");   // 1行ぶん表示したら改行
    }
    return 0;
}

実行結果:0 1 2 34 5 6 7 の2行

解説:2次元配列はforの二重ループとセットで使います。外側のforが「行」、内側のforが「列」を動かし、1行ぶん表示し終えたら改行する。この「二重ループで表をなめる」形は、画像処理やゲーム盤面など、あらゆる場面で再登場する基本パターンです。

発展 Q6

センサー値の記録 {512, 480, 1023, 505, 990} の中に、900以上の値(異常値とみなす)が何個あるかを数えて表示してください。ifとforと配列の総合問題です。

解答・解説を見る
q6.c

#include <stdio.h>

int main(void) {
    int data[5] = {512, 480, 1023, 505, 990};
    int count = 0;
    for (int i = 0; i < 5; i++) {
        if (data[i] >= 900) {
            count++;            // 条件に合ったら数える
        }
    }
    printf("count=%d\n", count);
    return 0;
}

実行結果:count=2

解説:「配列をforでなめながら、ifで条件に合うものだけ数える」。この走査+条件カウントは、データ処理の頻出パターンです。1023と990の2個が900以上なので答えは2。しきい値を変えれば「エラーの回数」「規定値を超えた回数」など、いろいろな集計に応用できます。

発展 Q7

直近3件だけを記録する「ログ」を作ります。大きさ3の配列に、値 10, 20, 30, 40, 50 を順に入れていき、いっぱいになったら一番古いものから上書きしてください(リングバッファ)。最後に配列の中身を表示します。

解答・解説を見る
q7.c

#include <stdio.h>
#define SIZE 3

int main(void) {
    int log[SIZE] = {0};
    int index = 0;
    int inputs[5] = {10, 20, 30, 40, 50};

    for (int i = 0; i < 5; i++) {
        log[index] = inputs[i];       // 今の位置に書く
        index = (index + 1) % SIZE;   // 次の位置へ(末尾なら先頭に戻る)
    }

    for (int i = 0; i < SIZE; i++) {
        printf("%d ", log[i]);
    }
    printf("\n");
    return 0;
}

実行結果:40 50 30

解説:カギは index = (index + 1) % SIZE です。% SIZE(3で割った余り)にすると、位置が 0→1→2→0→1… とぐるぐる回ります。だから4個目・5個目は、一番古い 1020 の場所に上書きされ、最新3件だけが残ります(中身は最後に書いた位置の関係で 40 50 30 の並びになります)。大きさを増やさずに「直近N件」を持てるのがリングバッファの利点です。

この発展問題は余力がある人向けです。基礎5問ができていれば、次のテーマ(関数)に進んで大丈夫です。

例:実務で実際に書くコード

組込みで配列の出番が一番多いのが、「値の履歴を持つ」使い方です。センサー値は1回ごとにブレるので、直近の何個かを配列にためて平均すると、ノイズがならされて安定します。

例:直近5回のセンサー値を平均する

int history[5];        // 直近5回ぶんの入れ物

// (メインループの中で)新しい値を入れて、平均を出す
int sum = 0;
for (int i = 0; i < 5; i++) {
    sum += history[i];
}
int average = sum / 5;  // ノイズがならされた値
実務で使用する際は「この時刻にこの操作をした」という動作の履歴(ログ)を、固定長の配列で内部に持ったり、センサ取得値を排列で持ったりなどでよく使用します。

他にも、通信データも中身は配列でデータをやり取りすることが多く。CANやシリアル通信で受け取るデータは、バイト(数値)の並び=配列で定義されているため、ここでも配列を使ったりしますね。
そのため配列は実務でも頻繁にでてくるコードの書き方なので確実に理解しておいていただきたいです。

最後につまずきポイントをおさらい

添字は0から。「5個なら0〜4」 一番多いミスが、存在しない番号に触ってしまうことです。5個の配列で使えるのは 0〜4 で、data[5] はありません。for の条件を i <= 5 と書くと、この配列外アクセスが起きます。i < 要素数 と覚えてください。PC上ではたまたま動くこともありますが、マイコンでは他のデータを壊す原因になります。

もう一つは 初期化忘れです。int data[5]; と宣言しただけの配列には、何が入っているか分かりません(ゴミの値)。合計などに使う前に、{0} で初期化するか、値を入れてから使いましょう。

よくある質問(FAQ)

配列の要素数は後から変えられる?

基本のC言語の配列は、宣言した大きさのままです。組込みでは、この「固定の大きさ」がむしろ普通で、必要な最大数を最初に決めて確保します。だから「直近N件」のような使い方(リングバッファ)が定番になります。

要素数を数字で書かず、まとめて管理したい

#define SIZE 5 のように定数で決めておくと、int data[SIZE];for (i < SIZE) で使い回せて、後から数を変えるのも1か所で済みます。定数の使い方は#1 変数・型・演算子を参照してください。

文字列と配列は関係ある?

大ありです。C言語の文字列の正体はchar型の配列(文字の並び)です。配列に慣れておくと、文字列の扱いもすんなり理解できます。詳しくはポインタの記事で触れます。

次に読む

データをまとめて扱えるようになったら、次は「処理をまとめる」関数です。配列と関数を組み合わせると、コードが一気に整理できます。

次のドリル(#5)C言語 関数の練習問題7問|引数・戻り値・スコープを固める 前のドリル(#3)C言語 ループの練習問題7問|for・while・do-whileを固める 目次へ戻るC言語 基礎ドリル|組込みに必要な8テーマの練習問題まとめ