C言語の基本|制御文で配列を処理する

配列を順番に調べ、条件に合わせて選び出す。for文とif文を組み合わせれば、並んだデータから必要な情報を取り出せます。

配列は、同じ型の値をまとめて保存するための便利な仕組みです。しかし、値を入れて表示するだけでは、配列の力を十分に活用できません。

保存されたデータから、条件を満たす要素の個数を数えたり、最大値や最小値を探したり、必要な値だけを別の配列へコピーしたりするには、制御文との組み合わせが必要です。

基本となる役割分担は、とてもシンプルです。

  • for文で配列の要素を順番に調べる
  • if文で現在の要素が条件に合うか判定する
  • 条件に応じてカウント、比較、コピー、入れ替えなどを行う

この考え方を身につけると、配列は単なる値の置き場所ではなく、集計や検索に利用できる実用的なデータ構造になります。

ここでは、条件に合う要素のカウント、最大値と最小値の探索、合計と平均の計算、条件付きコピー、逆順への入れ替え、横棒グラフの表示を通して、制御文による配列処理を詳しく見ていきます。

制御文で配列を処理するとは

制御文で配列を処理するとは、配列の各要素を順番に取り出し、値や位置に応じて処理内容を変えることです。

よく使われる処理には、次のようなものがあります。

処理内容
繰り返し配列の先頭から最後まで順番に調べる
条件判定指定した条件に合う要素だけを処理する
カウント条件に合う要素の個数を数える
集計合計や平均を求める
比較最大値や最小値を探す
コピー条件に合う要素を別の配列へ入れる
入れ替え左右の要素を交換して逆順にする
表現数値に応じて記号を並べる

多くの配列処理では、次の形が基本になります。

for (size_t i = 0; i < count; i++) {
    if (array[i]が条件に合う) {
        /* 必要な処理 */
    }
}

for文は、配列のどの要素を調べるかを管理します。if文は、取り出した要素を実際に処理するかどうかを判断します。

図1:for文とif文による配列処理の基本

この図から分かること

for文は、配列の先頭から末尾まで要素を順番に取り出します。取り出した要素はif文で判定され、条件を満たした場合だけ必要な処理が行われます。

条件に合わない要素があっても、for文は終了しません。次の要素へ進み、配列の最後まで確認を続けます。

配列の要素数を求めてから処理する

配列全体を安全に処理するには、要素数を正しく把握する必要があります。

次の式を使うと、配列本体から要素数を求められます。

size_t count = sizeof(array) / sizeof(array[0]);
意味
sizeof(array)配列全体のバイト数
sizeof(array[0])要素1個分のバイト数
全体 ÷ 1個分配列の要素数

求めた要素数は、for文の継続条件に利用できます。

for (size_t i = 0; i < count; i++) {
    /* array[i]を処理する */
}

配列の要素を増減しても、countが自動的に変化します。for文の繰り返し回数を手作業で直さずに済むため、修正漏れや範囲外アクセスを防ぎやすくなります。

条件に合う要素の個数を数える

配列から条件に合う要素の個数を求める場合は、カウント用の変数を用意します。

int match_count = 0;

for (size_t i = 0; i < count; i++) {
    if (条件式) {
        match_count++;
    }
}

ポイントは、match_countを0で初期化することです。

配列の要素が条件に合うたびにmatch_count++を実行します。条件に合わない場合は何もせず、次の要素へ進みます。

稼働率の高い設備数と最大値・最小値を調べるプログラム

ファイル名:10_6_1.c

#include <stdio.h>

int main(void)
{
    int operation_rates[] = {42, 68, 55, 81, 47, 73, 60};
    size_t machine_count =
        sizeof(operation_rates) / sizeof(operation_rates[0]);

    int high_rate_count = 0;

    /* 稼働率が65%以上の設備数を数える */
    for (size_t i = 0; i < machine_count; i++) {
        if (operation_rates[i] >= 65) {
            high_rate_count++;
        }
    }

    printf("稼働率が65%%以上の設備は%d台です。\n",
           high_rate_count);

    /* 最大値と最小値を先頭要素で初期化する */
    int max_rate = operation_rates[0];
    int min_rate = operation_rates[0];

    /* 2番目の要素から順番に比較する */
    for (size_t i = 1; i < machine_count; i++) {
        if (operation_rates[i] > max_rate) {
            max_rate = operation_rates[i];
        }

        if (operation_rates[i] < min_rate) {
            min_rate = operation_rates[i];
        }
    }

    printf("最も高い稼働率は%d%%です。\n", max_rate);
    printf("最も低い稼働率は%d%%です。\n", min_rate);

    return 0;
}
実行結果の例
稼働率が65%以上の設備は3台です。
最も高い稼働率は81%です。
最も低い稼働率は42%です。
条件に合う設備数を数える処理

最初のfor文では、配列のすべての要素を調べています。

for (size_t i = 0; i < machine_count; i++) {
    if (operation_rates[i] >= 65) {
        high_rate_count++;
    }
}

処理の流れは次のとおりです。

稼働率65以上かhigh_rate_count
42いいえ0
68はい1
55いいえ1
81はい2
47いいえ2
73はい3
60いいえ3

最終的に、68、81、73の3要素が条件を満たします。

printfの書式文字列にある%%は、画面へ%を1つ表示するための書き方です。

printf("%d%%\n", max_rate);

最大値と最小値は先頭要素で初期化する

最大値と最小値を探すときは、配列の先頭要素を最初の比較基準にします。

int max_rate = operation_rates[0];
int min_rate = operation_rates[0];

0で初期化すると、配列の内容によっては正しい結果を得られません。たとえば、すべての要素が負の値だった場合、0は配列に含まれていないにもかかわらず最大値として残ってしまいます。

配列内の値で初期化すれば、比較結果も必ず配列の要素になります。

先頭要素はすでに初期値として確認済みなので、比較用のfor文は添字1から始めます。

for (size_t i = 1; i < machine_count; i++) {
    if (operation_rates[i] > max_rate) {
        max_rate = operation_rates[i];
    }

    if (operation_rates[i] < min_rate) {
        min_rate = operation_rates[i];
    }
}

最大値の処理では、現在の要素がmax_rateより大きい場合だけ更新します。最小値では、現在の要素がmin_rateより小さい場合だけ更新します。

図2:最大値と最小値を更新する流れ

この図から分かること

先頭要素を最大値と最小値の初期値にすれば、その後は残りの要素と比較するだけで結果を求められます。

現在の最大値より大きい値が見つかったときは最大値を更新し、現在の最小値より小さい値が見つかったときは最小値を更新します。比較を最後まで続けることで、配列全体の最大値と最小値が残ります。

for文とif文の役割を分けて考える

配列を制御文で処理するときは、for文とif文の役割を分けるとコードを理解しやすくなります。

制御文主な役割
for文配列の要素を順番に取り出す
if文現在の要素が条件を満たすか調べる

たとえば、偶数の個数を数える処理なら、for文が全要素を順番に確認し、if文が偶数かどうかを判断します。

int even_count = 0;

for (size_t i = 0; i < count; i++) {
    if (numbers[i] % 2 == 0) {
        even_count++;
    }
}

この役割分担は、正の数の個数、指定値以上の件数、特定の文字の出現数などにも応用できます。

配列処理で使う定番パターン

条件に合う要素を数える

int match_count = 0;

for (size_t i = 0; i < count; i++) {
    if (条件式) {
        match_count++;
    }
}

カウント用変数を0で初期化し、条件を満たしたときだけ1増やします。

合計を求める

int total = 0;

for (size_t i = 0; i < count; i++) {
    total += array[i];
}

合計用変数も0で初期化します。その後、すべての要素を順番に加算します。

最大値と最小値を求める

int max_value = array[0];
int min_value = array[0];

for (size_t i = 1; i < count; i++) {
    if (array[i] > max_value) {
        max_value = array[i];
    }

    if (array[i] < min_value) {
        min_value = array[i];
    }
}

配列に1個以上の要素があることを前提として、先頭要素を比較の基準にします。

条件に合う要素をコピーする

size_t destination_index = 0;

for (size_t i = 0; i < count; i++) {
    if (条件式) {
        destination[destination_index] = source[i];
        destination_index++;
    }
}

元の配列を調べる添字と、コピー先の位置を表す添字を分けることがポイントです。

左右の要素を入れ替える

for (size_t left = 0; left < count / 2; left++) {
    size_t right = count - 1 - left;

    int temp = array[left];
    array[left] = array[right];
    array[right] = temp;
}

leftは左側、rightは対応する右側の添字です。tempは、上書きされる値を一時的に保存します。

合計と平均を求める

配列の平均値は、最初に全要素の合計を求め、その合計を要素数で割って計算します。

整数同士で割り算すると小数部分が切り捨てられるため、平均を小数で求める場合は型変換が必要です。

6日分の発送個数から合計と平均を求めるプログラム

ファイル名:10_6_2.c

#include <stdio.h>

int main(void)
{
    int shipments[] = {16, 21, 13, 24, 18, 10};
    size_t day_count =
        sizeof(shipments) / sizeof(shipments[0]);

    int total = 0;

    /* 全日分の発送個数を合計する */
    for (size_t i = 0; i < day_count; i++) {
        total += shipments[i];
    }

    double average = (double)total / day_count;

    printf("合計個数:%d\n", total);
    printf("平均個数:%.1f\n", average);

    return 0;
}
実行結果の例
合計個数:102
平均個数:17.0
合計を求める処理

合計用のtotalは、加算を始める前に0で初期化します。

int total = 0;

for文の中では、現在の要素をtotalへ加えています。

total += shipments[i];

この複合代入は、次の式と同じ意味です。

total = total + shipments[i];

6要素すべてを加えると、合計は102になります。

平均で型変換が必要な理由

平均を求める式では、totalをdouble型へ変換しています。

double average = (double)total / day_count;

totalはint型、day_countはsize_t型です。そのまま割ると整数同士の計算になり、小数部分が失われます。

totalをdouble型へ変換すれば、割り算も浮動小数点数による計算になり、小数部分を含む平均を求められます。

条件に合う要素だけを別の配列へコピーする

条件付きコピーでは、2つの位置を同時に管理します。

  • 元の配列のどこを確認しているか
  • コピー先配列のどこへ保存するか

条件に合わない要素はコピーされないため、2つの添字が同じように増えるとは限りません。

12以上の測定値だけを別の配列へコピーするプログラム

ファイル名:10_6_3.c

#include <stdio.h>

int main(void)
{
    int measurements1[] = {5, 18, 7, 24, 12, 3, 20, 9};
    int measurements2[8];

    size_t measurement_count =
        sizeof(measurements1) / sizeof(measurements1[0]);

    size_t copied_count = 0;

    /* 12以上の測定値だけをコピーする */
    for (size_t i = 0; i < measurement_count; i++) {
        if (measurements1[i] >= 12) {
            measurements2[copied_count] = measurements1[i];
            copied_count++;
        }
    }

    /* コピーした要素だけを表示する */
    for (size_t i = 0; i < copied_count; i++) {
        printf("measurements2[%zu] = %d\n",
               i, measurements2[i]);
    }

    return 0;
}
実行結果の例
measurements2[0] = 18
measurements2[1] = 24
measurements2[2] = 12
measurements2[3] = 20
2つの添字が必要になる理由

最初のfor文で使うiは、measurements1の確認位置を表します。

measurements1[i]

copied_countは、measurements2へ次の値を保存する位置を表します。

measurements2[copied_count]

2つの変数の役割は次のとおりです。

変数役割増えるタイミング
i元の配列を確認する位置繰り返すたびに増える
copied_countコピー先の保存位置コピーしたときだけ増える

たとえば、measurements1[0]の5は12未満なのでコピーされません。このときiは次へ進みますが、copied_countは0のままです。

measurements1[1]の18は条件を満たすため、measurements2[0]へ保存されます。その後、copied_countが1になります。

表示用のfor文では、配列全体の8要素ではなく、実際にコピーしたcopied_count個だけを表示します。値を入れていない要素を参照しないことが大切です。

配列の要素を左右逆順にする

配列を逆順にするには、左端と右端の要素を交換しながら、中央へ近づけていきます。

要素数が6なら、交換する組み合わせは次のとおりです。

交換回数左側右側
1回目添字0添字5
2回目添字1添字4
3回目添字2添字3

右側の添字は、次の式で求められます。

right = count - 1 - left;

すべての要素を交換する必要はありません。配列の中央まで処理すれば、後半部分も同時に入れ替わります。そのため、繰り返し回数はcount / 2です。

図3:左右の要素を交換して逆順にする流れ

この図から分かること

配列を逆順にするには、左側の要素と対応する右側の要素を交換します。leftが0ならrightは5、leftが1ならrightは4という関係です。

左右をcount / 2回交換すれば、すべての要素が逆順になります。同じ組み合わせをもう一度交換しないように、中央までで処理を終了することがポイントです。

路線コードの並びを逆順にするプログラム

ファイル名:10_6_4.c

#include <stdio.h>

int main(void)
{
    int route_codes[] = {101, 205, 309, 412, 518, 623};
    size_t route_count =
        sizeof(route_codes) / sizeof(route_codes[0]);

    /* 左右の要素を交換する */
    for (size_t left = 0; left < route_count / 2; left++) {
        size_t right = route_count - 1 - left;

        int temp = route_codes[left];
        route_codes[left] = route_codes[right];
        route_codes[right] = temp;
    }

    /* 交換後の配列を表示する */
    for (size_t i = 0; i < route_count; i++) {
        printf("route_codes[%zu] = %d\n",
               i, route_codes[i]);
    }

    return 0;
}
実行結果の例
route_codes[0] = 623
route_codes[1] = 518
route_codes[2] = 412
route_codes[3] = 309
route_codes[4] = 205
route_codes[5] = 101
作業用変数tempの役割

2つの変数の値を交換するときは、一方の値を一時的に保存する必要があります。

int temp = route_codes[left];
route_codes[left] = route_codes[right];
route_codes[right] = temp;

最初に左側の値をtempへ保存します。その後、右側の値を左側へ代入し、最後にtempへ保存しておいた値を右側へ代入します。

tempを使わずに次のように書くと、左側の値が失われます。

route_codes[left] = route_codes[right];
route_codes[right] = route_codes[left];

1行目を実行した時点で、route_codes[left]の元の値が上書きされます。2行目ではすでに上書きされた値を使うため、左右が同じ値になってしまいます。

二重のfor文で数値を横棒グラフにする

配列の数値を記号の個数に変換すると、簡単な横棒グラフを作れます。

この処理では、2つのfor文に異なる役割を持たせます。

for文役割
外側のfor文配列の各要素を順番に処理する
内側のfor文数値に応じた個数の記号を表示する

外側のfor文が曜日を1つ選び、内側のfor文がその曜日の販売数に応じて*を表示します。

1週間の販売数を横棒グラフで表示するプログラム

ファイル名:10_6_5.c

#include <stdio.h>

int main(void)
{
    const char *days[] = {
        "月", "火", "水", "木", "金", "土", "日"
    };

    int sales[] = {9, 14, 7, 16, 11, 18, 10};
    size_t day_count = sizeof(sales) / sizeof(sales[0]);

    printf("1週間の販売数\n\n");

    for (size_t i = 0; i < day_count; i++) {
        printf("%s(%d個):", days[i], sales[i]);

        int star_count = sales[i] / 2;

        for (int j = 0; j < star_count; j++) {
            printf("*");
        }

        printf("\n");
    }

    return 0;
}
実行結果の例
1週間の販売数

月(9個):****
火(14個):*******
水(7個):***
木(16個):********
金(11個):*****
土(18個):*********
日(10個):*****
外側のfor文の役割

外側のfor文は、月曜日から日曜日までのデータを順番に処理します。

for (size_t i = 0; i < day_count; i++) {

days[i]は曜日、sales[i]は同じ位置にある販売数です。

printf("%s(%d個):", days[i], sales[i]);

daysとsalesは、同じ添字に関連するデータが入るように並べています。

添字daysの要素salesの要素
09
114
27
316
411
518
610
内側のfor文の役割

販売数2個につき、*を1個表示します。

int star_count = sales[i] / 2;

たとえば火曜日は14個なので、次の計算になります。

14 ÷ 2 = 7

内側のfor文は7回繰り返し、*を7個表示します。

for (int j = 0; j < star_count; j++) {
    printf("*");
}

整数同士の割り算なので、販売数が奇数の場合は小数部分が切り捨てられます。月曜日の9個は9 ÷ 2で4となり、*は4個表示されます。

配列処理を組み立てるときの考え方

配列を使った問題では、最初からコードを書き始めるより、次の順番で整理すると考えやすくなります。

何を順番に調べるのか

まず、対象となる配列を確認します。

array[i]

for文を使って、先頭の添字0から最後の要素まで順番に処理します。

条件判定が必要か

次のような指定がある場合は、if文を使います。

  • 12以上の値だけを選ぶ
  • 偶数だけを数える
  • 現在の最大値より大きいか調べる
  • 正の値だけを合計する

条件がなければ、合計処理のようにfor文だけで書ける場合もあります。

何を結果として残すのか

求める結果によって、必要な変数が変わります。

求める結果用意するもの
条件に合う個数0で初期化したカウント変数
合計0で初期化した合計変数
平均合計と要素数
最大値・最小値先頭要素で初期化した比較変数
条件付きコピーコピー先配列と保存位置
逆順左右の添字と作業用変数
横棒グラフ記号数と内側のfor文

配列を制御文で扱うときの注意点

注意点確認する内容
添字の範囲0から要素数 - 1までにする
要素数配列本体からsizeofで求める
カウントと合計処理前に0で初期化する
最大値と最小値配列の先頭要素で初期化する
条件付きコピーコピーした個数だけ表示する
コピー先の大きさ必要な要素を保存できる大きさにする
要素の交換tempで元の値を一時保存する
二重ループ外側と内側の役割を分ける

for文で全要素をたどり、if文で必要なデータを選び、目的に応じた変数へ結果を残す。この流れを意識すると、件数、合計、最大値、コピー、入れ替えなどの処理を整理して書けるようになります。