C言語のきほん|制御文で配列を処理する

配列は並べるだけじゃもったいない。制御文を使えば、データの意味を見つけて活かせます。

1次元配列は、同じ型のデータを順番にまとめて扱える、とても便利な仕組みです。
ただし、配列の本当の力が発揮されるのは、値をただ保存するときではなく、繰り返し処理や条件分岐と組み合わせて使うときです。

たとえば、配列の中から条件に合う値だけを数えたり、大きい値や小さい値を探したり、必要なデータだけを別の配列へ取り出したりといった処理は、C言語ではとてもよく登場します。こうした処理では、for文で順番に配列要素を見ていき、if文で必要な判定を行う、という流れが基本になります。

前の内容では、for文を使って配列の要素を順番に取り出す方法を学びました。今回はそこから一歩進んで、for文とif文を組み合わせて、配列の中身を調べたり、条件に応じて処理を分けたりする方法を見ていきます。

この考え方が身につくと、配列を使ったプログラムがぐっと実践的になります。
単に表示するだけではなく、

  • 条件に合うデータの個数を数える
  • 最大値や最小値を見つける
  • 特定のデータだけを取り出す
  • 並び順を入れ替える
  • 数値を見やすい形で表現する

といった処理ができるようになります。

配列と制御文は、C言語の基本の中でも特に大切な組み合わせです。
このあたりをしっかり押さえておくと、今後のプログラム作成がかなり書きやすくなります。ここでは、やさしく順を追いながら、配列を制御文で処理する考え方を丁寧に見ていきましょう。

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

「制御文で配列を処理する」とは、配列の各要素に対して、for文やif文を使いながら何らかの操作を行うことです。

代表的な処理には、次のようなものがあります。

処理の種類内容
繰り返し処理配列の先頭から末尾まで順番に取り出す
条件判定条件に合う要素だけを数える、表示する
集計合計、平均、件数を求める
比較最大値、最小値を探す
コピー条件に合う要素だけ別の配列へ移す
入れ替え要素の順番を逆にする、並べ替える

このような処理では、まず for文で配列を最初から最後まで見ていくのが基本です。
その途中で、if文を使って「この値は条件に合っているかな」と判定し、必要な処理を行います。

流れを図で表すと、次のようなイメージです。

この流れが理解できると、配列を使った問題の多くに対応できるようになります。

サンプルプログラムの考え方

元の例では、点数配列から合格者数、最高点、最低点を求めていました。
ここでは内容を変えて、1週間の気温データから、暑い日の件数、最高気温、最低気温を求めるシンプルな例で見ていきましょう。

気温の配列を順番に見ながら、

  • 30度以上の日が何日あるか数える
  • 一番高い気温を見つける
  • 一番低い気温を見つける

という処理を行います。

サンプルプログラム

ファイル名:10_6_1.c

#include <stdio.h>

int main(void)
{
    int temperatures[] = {28, 31, 25, 33, 29, 35, 27};
    size_t day_count = sizeof(temperatures) / sizeof(temperatures[0]);

    // 30度以上の日数を数える
    int hot_day_count = 0;
    for (size_t i = 0; i < day_count; i++) {
        if (temperatures[i] >= 30) {
            hot_day_count++;
        }
    }

    printf("30度以上の日は %d 日です。\n", hot_day_count);

    // 最高気温と最低気温を求める
    int max_temp = temperatures[0];
    int min_temp = temperatures[0];

    for (size_t i = 1; i < day_count; i++) {
        if (temperatures[i] > max_temp) {
            max_temp = temperatures[i];
        }

        if (temperatures[i] < min_temp) {
            min_temp = temperatures[i];
        }
    }

    printf("最高気温は %d 度です。\n", max_temp);
    printf("最低気温は %d 度です。\n", min_temp);

    return 0;
}

実行結果例

30度以上の日は 3 日です。
最高気温は 35 度です。
最低気温は 25 度です。

このプログラムで使われている考え方

このプログラムには、配列処理の基本がたくさん詰まっています。
ここを丁寧に理解すると、今後の応用がしやすくなります。

配列の要素数を求める

最初に、次の式で配列の要素数を求めています。

size_t day_count = sizeof(temperatures) / sizeof(temperatures[0]);

これは、配列全体のサイズを1要素分のサイズで割って、何個入っているかを求める定番の書き方です。

意味
sizeof(temperatures)配列全体のサイズ
sizeof(temperatures[0])先頭要素1個分のサイズ
全体 ÷ 1個分要素数

要素数を自動で求めておくと、配列の中身を増減させても for文の回数を手で直さなくてよいので便利です。

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

次の部分では、30度以上の日を数えています。

int hot_day_count = 0;
for (size_t i = 0; i < day_count; i++) {
    if (temperatures[i] >= 30) {
        hot_day_count++;
    }
}

この処理の流れはとても大事です。

  1. カウント用の変数を 0 で初期化する
  2. for文で配列を最初から最後まで調べる
  3. if文で条件を満たすか判定する
  4. 条件を満たしたら件数を1増やす

この「数える」という処理は、配列問題で非常によく出てきます。
たとえば、

  • 70点以上の人数
  • 偶数の個数
  • 正の数の個数
  • 特定の文字の出現回数

など、さまざまな場面に応用できます。

最大値と最小値を求める

次の部分では、最高気温と最低気温を求めています。

int max_temp = temperatures[0];
int min_temp = temperatures[0];

ここでのポイントは、最初の要素で初期化していることです。

なぜ 0 で初期化しないのかというと、配列の中身によっては正しい結果にならないことがあるからです。
たとえば、すべて負の値だった場合、最大値を 0 にしてしまうと、本来配列内にない 0 が最大値になってしまいます。

そのため、最大値や最小値を求めるときは、まず配列の最初の要素を初期値にするのが基本です。

その後、残りの要素と順番に比較します。

for (size_t i = 1; i < day_count; i++) {
    if (temperatures[i] > max_temp) {
        max_temp = temperatures[i];
    }

    if (temperatures[i] < min_temp) {
        min_temp = temperatures[i];
    }
}

ここでは、インデックスを 1 から始めています。
これは、0番目の要素はすでに max_temp と min_temp の初期値として使っているためです。

比較の流れを図で表すと、こんなイメージです。

for文とif文の役割分担

配列処理では、for文とif文の役割を分けて考えると分かりやすいです。

制御文役割
for文配列を順番に見る
if文条件に合うか判定する

つまり、

  • for文は全件チェック担当
  • if文は条件チェック担当

というイメージです。

この組み合わせは、配列だけでなく、文字列処理やデータ集計など、いろいろな場面で使われます。

配列処理でよく使う基本パターン

配列を制御文で処理するときには、いくつかの定番パターンがあります。
このパターンを覚えておくと、問題を見たときに考えやすくなります。

パターン1 個数を数える

int count = 0;

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

この形は、条件に合う要素の数を求めるときに使います。

パターン2 最大値・最小値を求める

int max = array[0];
int min = array[0];

for (size_t i = 1; i < count_of_array; i++) {
    if (array[i] > max) {
        max = array[i];
    }
    if (array[i] < min) {
        min = array[i];
    }
}

この形は、配列の中から一番大きい値や小さい値を見つけるときの基本です。

パターン3 条件に合う要素を別の配列へ入れる

int result[元の配列の要素数];
int index = 0;

for (size_t i = 0; i < count_of_array; i++) {
    if (条件) {
        result[index] = array[i];
        index++;
    }
}

この形は、偶数だけ取り出す、特定の条件に合う値だけ集める、といった処理に使います。

パターン4 要素を入れ替える

int result[元の配列の要素数];
int index = 0;

for (size_t i = 0; i < count_of_array; i++) {
    if (条件) {
        result[index] = array[i];
        index++;
    }
}

この形は、配列を逆順にするときなどによく使います。

配列処理を考えるときのコツ

配列の問題に慣れないうちは、何をすればよいか分からなくなることがあります。
そんなときは、次の順番で整理すると考えやすいです。

何を1個ずつ見ていくのかを考える

まずは「配列の各要素を順番に見る」ということを意識します。
ほとんどの問題はここから始まります。

条件があるかどうかを考える

偶数だけ、70以上だけ、正の値だけ、という条件があるなら if文が必要です。

結果として何を求めるのかを考える

求めたいものが、

  • 件数なのか
  • 合計なのか
  • 最大値なのか
  • コピー後の配列なのか

によって、用意する変数や処理が変わります。

この整理を表にすると次のようになります。

考えること
何を順番に見るか配列の各要素
条件はあるか30度以上、偶数、正の数など
結果は何か件数、最大値、最小値、合計、コピー先

実践問題

6日分の売上個数を配列として宣言し、合計個数と平均個数を求めるプログラムを作成してください。

実行結果例
配列の要素が {14, 18, 12, 20, 16, 10} の場合

合計個数: 90
平均個数: 15.0

for文で合計を求めてから、平均を計算してみましょう。

解答例

ファイル名:10_6_2.c

#include <stdio.h>

int main(void)
{
    int sales[] = {14, 18, 12, 20, 16, 10};
    size_t count = sizeof(sales) / sizeof(sales[0]);

    int total = 0;

    // 合計個数を求める
    for (size_t i = 0; i < count; i++) {
        total += sales[i];
    }

    double average = (double)total / count;

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

    return 0;
}

解説

この問題では、配列の全要素を順番に足していく処理が中心になります。

total += sales[i];

は、

total = total + sales[i];

と同じ意味です。
これを for文の中で繰り返すことで、全要素の合計を求められます。

平均は、

(double)total / count

としています。
ここで double に型変換しているのは、小数点を含む平均値を正しく求めるためです。
整数同士の割り算のままだと、小数部分が切り捨てられてしまうので注意が必要です。

実践問題

要素数8の整数型の配列 numbers1 のうち、10以上の要素だけを配列 numbers2 にコピーするプログラムを作成してください。

実行結果例
配列の要素が {4, 15, 8, 22, 10, 3, 19, 7} の場合

numbers2[0] = 15
numbers2[1] = 22
numbers2[2] = 10
numbers2[3] = 19

for文とif文を組み合わせて考えてみましょう。
numbers2 の要素数は numbers1 と同じだけ宣言してください。

解答例

ファイル名:10_6_3.c

#include <stdio.h>

int main(void)
{
    int numbers1[] = {4, 15, 8, 22, 10, 3, 19, 7};
    int numbers2[8];
    size_t count = sizeof(numbers1) / sizeof(numbers1[0]);

    int j = 0;

    // 10以上の要素を numbers2 にコピーする
    for (size_t i = 0; i < count; i++) {
        if (numbers1[i] >= 10) {
            numbers2[j] = numbers1[i];
            j++;
        }
    }

    // コピーした要素を表示する
    for (int i = 0; i < j; i++) {
        printf("numbers2[%d] = %d\n", i, numbers2[i]);
    }

    return 0;
}

解説

この問題では、for文で numbers1 を順番に見ながら、条件に合う要素だけを numbers2 に移しています。

ここで大切なのは、元の配列を見るための変数 i と、コピー先の位置を管理する変数 j を分けていることです。

変数役割
inumbers1 のどこを見ているか
jnumbers2 のどこへ入れるか

たとえば 10未満の値はコピーしないので、i は進んでも j は増えない場合があります。
この考え方は、条件付きコピーでとても重要です。

流れを簡単に表すと次のようになります。

numbers1 を先頭から見る
        ↓
10以上なら numbers2 に入れる
        ↓
入れたときだけ j を増やす

実践問題

要素数6の整数型の配列 data の要素を左右逆順に入れ替えるプログラムを作成してください。

実行結果例
配列の要素が {11, 24, 36, 48, 59, 62} の場合

data[0] = 62
data[1] = 59
data[2] = 48
data[3] = 36
data[4] = 24
data[5] = 11

入れ替えには作業用変数を使います。
左側と右側の位置関係に注目して考えてみましょう。

解答例

ファイル名:10_6_4.c

#include <stdio.h>

int main(void)
{
    int data[] = {11, 24, 36, 48, 59, 62};
    size_t count = sizeof(data) / sizeof(data[0]);

    // 配列の要素を逆順に入れ替える
    for (size_t left = 0; left < count / 2; left++) {
        size_t right = count - 1 - left;
        int temp = data[left];
        data[left] = data[right];
        data[right] = temp;
    }

    // 入れ替え後の配列を表示する
    for (size_t i = 0; i < count; i++) {
        printf("data[%zu] = %d\n", i, data[i]);
    }

    return 0;
}

解説

配列を逆順にするには、左右の要素を入れ替えていきます。

たとえば、要素数6なら、

  • 0番目 と 5番目
  • 1番目 と 4番目
  • 2番目 と 3番目

を入れ替えれば完成です。

図で考えると分かりやすいです。

ここでは left を左側の位置として使い、right を

count - 1 - left

で求めています。
この式によって、left と対になる右側の位置を見つけられます。

また、入れ替えには一時的に値を保管する temp が必要です。
temp を使わずに代入すると、元の値が上書きされて消えてしまうためです。

実践問題

ある店の1週間の来店者数を横棒グラフで表示するプログラムを作成してください。
ただし、グラフは 2人単位を * で表すものとします。

1週間の来店者数

曜日
来店者数81161410179

実行結果例

1週間の来店者数

月(8人): ****
火(11人): *****
水(6人): ***
木(14人): *******
金(10人): *****
土(17人): ********
日(9人): ****

配列に入った数値をもとに、for文を二重に使って横棒グラフを表示してみましょう。

解答例

ファイル名:10_6_5.c

#include <stdio.h>

int main(void)
{
    char *days[] = {"月", "火", "水", "木", "金", "土", "日"};
    int visitors[] = {8, 11, 6, 14, 10, 17, 9};
    size_t count = sizeof(visitors) / sizeof(visitors[0]);

    printf("1週間の来店者数\n\n");

    for (size_t i = 0; i < count; i++) {
        printf("%s(%d人): ", days[i], visitors[i]);

        int star_count = visitors[i] / 2;
        for (int j = 0; j < star_count; j++) {
            printf("*");
        }

        printf("\n");
    }

    return 0;
}

解説

この問題では、for文を二重に使っています。

外側のfor文

外側の for文 は、曜日ごとのデータを順番に処理するためのものです。

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

これによって、月曜日から日曜日までを順番に表示できます。

内側のfor文

内側の for文 は、* を必要な個数だけ表示するためのものです。

int star_count = visitors[i] / 2;
for (int j = 0; j < star_count; j++) {
    printf("*");
}

たとえば来店者数が 8 人なら、

8 / 2 = 4

なので、* を4個表示します。

このように、数値データを記号の繰り返しに変換することで、簡単なグラフを作れます。
画面に見やすく表現する練習として、とてもよい問題です。

この単元で身につけたいこと

今回の内容で特に大事なのは、「配列を1つずつ見ながら、条件に応じて処理する」という考え方です。
コードの形としてはシンプルでも、実際にはさまざまな応用につながります。

特に意識したいのは次の点です。

ポイント内容
配列は for文で順番に見る先頭から末尾まで処理する
条件があるときは if文を使う条件に合うか判定する
件数を数えるときは 0 で初期化する条件を満たすたびに増やす
最大値・最小値は最初の要素で初期化する比較の基準を配列内の値にする
条件付きコピーでは添字を分ける見る位置と入れる位置を別に考える
入れ替えでは作業用変数を使う値を失わずに交換する

こうした考え方を身につけると、配列をただ並べるだけでなく、意味のある情報として扱えるようになります。
for文とif文はC言語の基本ですが、配列と組み合わせることで一気に実用的になります。
サンプルを少しずつ書き換えながら、自分でも条件を変えて試してみると、理解がかなり深まります。