
C言語のきほん|2次元配列を二重ループで処理する
2次元配列は、表のようにデータをまとめて扱える便利な仕組みです。
たとえば、複数人分の売上データ、数日分の気温、クラスの成績一覧など、「行」と「列」で整理できる情報をひとまとめにできます。
ただし、2次元配列の中身を順番に取り出して処理したいときは、1次元配列のときのように for文を1つ使うだけでは足りません。
そこで活躍するのが、for文の二重ループです。
外側のループで行を順番に進め、内側のループで列を順番に進めることで、2次元配列の全要素にきちんとアクセスできます。
この考え方を身につけると、各行の合計、各列の合計、平均値の計算などがとても自然に書けるようになります。
ここでは、元の「学生の点数」の例を別のわかりやすい題材に置き換えて、2次元配列を二重ループで処理する流れを丁寧に見ていきます。
今回は、3人の店員が4日間に記録した販売個数を2次元配列で管理し、次の内容を求めるプログラムにしてみます。
- 各店員の合計販売数と平均販売数
- 各日の合計販売数と平均販売数
二重ループの役割が見えやすい題材なので、2次元配列にまだ慣れていない方にも理解しやすいはずです。
2次元配列とは何か
2次元配列は、1次元配列を縦横に広げたようなものです。
たとえば、今回の販売データは次のような表で考えられます。
| 店員\日 | 1日目 | 2日目 | 3日目 | 4日目 |
|---|---|---|---|---|
| 1人目 | 12 | 15 | 11 | 14 |
| 2人目 | 10 | 18 | 13 | 16 |
| 3人目 | 9 | 14 | 12 | 15 |
この表をC言語では、たとえば次のような2次元配列で表せます。
int sales[3][4] = {
{12, 15, 11, 14},
{10, 18, 13, 16},
{9, 14, 12, 15}
};このとき、sales[行][列] という形で要素を取り出します。
| 要素 | 意味 |
|---|---|
| sales[0][0] | 1人目の1日目の販売数 |
| sales[0][1] | 1人目の2日目の販売数 |
| sales[1][2] | 2人目の3日目の販売数 |
| sales[2][3] | 3人目の4日目の販売数 |
つまり、最初の添字が行、次の添字が列を表しています。
二重ループが必要になる理由
2次元配列は、行ごとに列をたどることで全体を順番に処理できます。
そのため、for文を2つ組み合わせて使います。
基本形は次の通りです。
for (int i = 0; i < 行数; i++) {
for (int j = 0; j < 列数; j++) {
/* 配列[i][j] を処理する */
}
}この形では、
- 外側の for文 が行を進める
- 内側の for文 が列を進める
という役割になります。
今回の販売データなら、外側で店員を順に見て、内側で各日を順に見ていく流れになります。
サンプルプログラム
元の点数管理プログラムを、販売個数の集計プログラムに変更した例です。
表示メッセージも日本語でわかりやすくし、コメントもすべて日本語にしています。
ファイル名:10_9_1.c
#include <stdio.h>
#define STAFF_COUNT 3 // 店員数
#define DAY_COUNT 4 // 日数
int main(void)
{
// 販売個数を2次元配列で管理
int sales[STAFF_COUNT][DAY_COUNT] = {
{12, 15, 11, 14}, // 1人目の販売個数
{10, 18, 13, 16}, // 2人目の販売個数
{9, 14, 12, 15} // 3人目の販売個数
};
int staff_totals[STAFF_COUNT] = {0}; // 各店員の合計販売数
double staff_averages[STAFF_COUNT] = {0.0}; // 各店員の平均販売数
// 各店員の合計販売数と平均販売数を計算
for (int i = 0; i < STAFF_COUNT; i++) {
for (int j = 0; j < DAY_COUNT; j++) {
staff_totals[i] += sales[i][j];
}
staff_averages[i] = (double)staff_totals[i] / DAY_COUNT;
}
// 各店員の結果を表示
printf("各店員の合計販売数と平均販売数\n");
for (int i = 0; i < STAFF_COUNT; i++) {
printf("店員%d: 合計=%d, 平均=%.2f\n",
i + 1, staff_totals[i], staff_averages[i]);
}
int day_totals[DAY_COUNT] = {0}; // 各日の合計販売数
double day_averages[DAY_COUNT] = {0.0}; // 各日の平均販売数
// 各日の合計販売数と平均販売数を計算
for (int j = 0; j < DAY_COUNT; j++) {
for (int i = 0; i < STAFF_COUNT; i++) {
day_totals[j] += sales[i][j];
}
day_averages[j] = (double)day_totals[j] / STAFF_COUNT;
}
// 各日の結果を表示
printf("\n各日の合計販売数と平均販売数\n");
for (int j = 0; j < DAY_COUNT; j++) {
printf("%d日目: 合計=%d, 平均=%.2f\n",
j + 1, day_totals[j], day_averages[j]);
}
return 0;
}実行結果
各店員の合計販売数と平均販売数
店員1: 合計=52, 平均=13.00
店員2: 合計=57, 平均=14.25
店員3: 合計=50, 平均=12.50
各日の合計販売数と平均販売数
1日目: 合計=31, 平均=10.33
2日目: 合計=47, 平均=15.67
3日目: 合計=36, 平均=12.00
4日目: 合計=45, 平均=15.00プログラムの全体像をつかもう
このプログラムでは、同じ2次元配列 sales を使って、2つの方向から集計しています。
1つ目は、店員ごとの集計です。
これは「横方向」にデータを見ていくイメージです。
2つ目は、日ごとの集計です。
これは「縦方向」にデータを見ていくイメージです。
同じ表でも、どちらを外側のループにするかで、集計の視点が変わるのがポイントです。
各店員の合計販売数と平均販売数の計算
まずは、各店員が4日間で合計いくつ販売したのか、そして平均でいくつ販売したのかを求めています。
該当部分はこちらです。
for (int i = 0; i < STAFF_COUNT; i++) {
for (int j = 0; j < DAY_COUNT; j++) {
staff_totals[i] += sales[i][j];
}
staff_averages[i] = (double)staff_totals[i] / DAY_COUNT;
}ここでは、外側のループの変数 i が店員を表し、内側のループの変数 j が日を表しています。
流れを表にすると、次のようになります。
| ループ | 役割 |
|---|---|
| 外側の for文 | 店員を1人ずつ選ぶ |
| 内側の for文 | その店員の1日目〜4日目の販売数を順に足す |
たとえば i が 0 のときは、1人目のデータを処理します。
そのとき内側のループでは、
- sales[0][0]
- sales[0][1]
- sales[0][2]
- sales[0][3]
を順番に足していきます。
つまり、
12 + 15 + 11 + 14 = 52
となり、1人目の合計販売数が 52 と求まります。
そのあとで、4日分なので 4 で割って平均を出します。
52 ÷ 4 = 13.00
という流れです。
各店員の集計イメージ
sales
↓ 1人目の4日分を加算
staff_totals[0]
↓ 4で割る
staff_averages[0]
sales
↓ 2人目の4日分を加算
staff_totals[1]
↓ 4で割る
staff_averages[1]
sales
↓ 3人目の4日分を加算
staff_totals[2]
↓ 4で割る
staff_averages[2]
処理の見方
この部分は、「行ごとの集計」と考えるとわかりやすいです。
表でいうと、1行ずつ横に見ていき、その行の合計と平均を求めています。
各日の合計販売数と平均販売数の計算
次に、1日目、2日目、3日目、4日目ごとに、全店員の販売数を合計し、平均を求めています。
該当部分はこちらです。
for (int j = 0; j < DAY_COUNT; j++) {
for (int i = 0; i < STAFF_COUNT; i++) {
day_totals[j] += sales[i][j];
}
day_averages[j] = (double)day_totals[j] / STAFF_COUNT;
}今度は先ほどと逆で、外側のループの変数 j が日を表し、内側のループの変数 i が店員を表しています。
つまり、1日ずつ取り出して、その日の全店員の販売数を足しているわけです。
| ループ | 役割 |
|---|---|
| 外側の for文 | 日を1日ずつ選ぶ |
| 内側の for文 | その日の全店員の販売数を順に足す |
たとえば j が 0 のときは、1日目のデータを処理します。
内側のループでは、
- sales[0][0]
- sales[1][0]
- sales[2][0]
を順番に足します。
つまり、
12 + 10 + 9 = 31
となり、1日目の合計販売数が 31 になります。
さらに、店員は3人なので 3 で割って平均を出します。
31 ÷ 3 = 10.33
という計算です。
各日の集計イメージ
sales
↓ 1日目の3人分を加算
day_totals[0]
↓ 3で割る
day_averages[0]
sales
↓ 2日目の3人分を加算
day_totals[1]
↓ 3で割る
day_averages[1]
sales
↓ 3日目の3人分を加算
day_totals[2]
↓ 3で割る
day_averages[2]
sales
↓ 4日目の3人分を加算
day_totals[3]
↓ 3で割る
day_averages[3]
処理の見方
この部分は、「列ごとの集計」と考えると理解しやすいです。
表でいうと、1列ずつ縦に見ていき、その列の合計と平均を求めています。
ループの向きを変えると集計対象が変わる
このプログラムのとても大事なポイントは、同じ2次元配列でも、外側と内側のループの組み合わせを変えるだけで、見方が変わるということです。
| 外側のループ | 内側のループ | 何を集計しているか |
|---|---|---|
| 店員 | 日 | 各店員の合計・平均 |
| 日 | 店員 | 各日の合計・平均 |
この違いを意識できるようになると、2次元配列の問題がかなり読みやすくなります。
{0}で初期化する意味
このプログラムでは、合計を保存する配列を次のように初期化しています。
int staff_totals[STAFF_COUNT] = {0};
int day_totals[DAY_COUNT] = {0};これはとてもよく使う書き方です。
配列を {0} で初期化すると、先頭要素だけでなく、すべての要素が 0 に初期化されます。
たとえば、
int numbers[5] = {0};と書いた場合、実際には次のような状態になります。
| 要素 | 値 |
|---|---|
| numbers[0] | 0 |
| numbers[1] | 0 |
| numbers[2] | 0 |
| numbers[3] | 0 |
| numbers[4] | 0 |
合計を求める配列は、最初の値がゴミ値のままだと正しい結果になりません。
そのため、最初にきちんと 0 クリアしておくことが大切です。
吹き出しで書かれていた内容は、まさにこの点を表しています。
{0}で初期化すると配列の全ての要素が0クリアできるよ。
これは実務でも学習でもよく出てくる便利な書き方なので、ぜひ覚えておきたいところです。
平均を計算するときに double を使う理由
平均を求める式では、次のように書いています。
staff_averages[i] = (double)staff_totals[i] / DAY_COUNT;
day_averages[j] = (double)day_totals[j] / STAFF_COUNT;ここで大事なのは、整数同士の割り算にならないようにしていることです。
たとえば、31 ÷ 3 は本来 10.333... ですが、int 同士で割ると小数部分が切り捨てられて 10 になってしまいます。
そこで、
(double)staff_totals[i]のように型変換を行い、計算全体を小数で行うようにしています。
| 書き方 | 結果のイメージ |
|---|---|
| 31 / 3 | 10 |
| (double)31 / 3 | 10.333333... |
平均値を正しく出したいときは、この型変換がとても重要です。
配列と添字の対応を整理しよう
2次元配列では、どの添字が何を表しているかを見失いやすいです。
そのため、最初に意味をはっきり整理しておくことが大切です。
今回の配列では次の通りです。
| 添字 | 意味 |
|---|---|
| i | 店員番号 |
| j | 日にち番号 |
| sales[i][j] | i番目の店員のj日目の販売数 |
この対応が頭に入っていると、ループの意味も自然に読めるようになります。
たとえば、
sales[i][j]を見たら、
「i番目の店員の、j日目の販売数」
と読めるようにしておくと理解がぐっと楽になります。
二重ループを読むコツ
二重ループは慣れないうちは少し複雑に見えますが、次の順番で読むと理解しやすいです。
1つ目のコツは、外側のループが何を固定しているかを見ること
for (int i = 0; i < STAFF_COUNT; i++)なら、店員を1人ずつ固定しています。
for (int j = 0; j < DAY_COUNT; j++)なら、日を1日ずつ固定しています。
2つ目のコツは、内側のループが何を動かしているかを見ること
外側で店員を固定して、内側で日を動かしているなら、各店員の1日目から4日目までを順に処理しています。
外側で日を固定して、内側で店員を動かしているなら、その日の全店員の値を順に処理しています。
3つ目のコツは、何に足し込んでいるかを見ること
staff_totals[i] += sales[i][j];なら、店員ごとの合計に足し込んでいます。
day_totals[j] += sales[i][j];なら、日ごとの合計に足し込んでいます。
この3つを意識すると、二重ループの読み取りがかなりしやすくなります。
図でイメージすると理解しやすい

この学習で身につけたいこと
今回の内容で特に身につけておきたいのは、次の3点です。
| 身につけたいこと | 内容 |
|---|---|
| 2次元配列の見方 | 行と列でデータを管理する考え方 |
| 二重ループの使い方 | 外側と内側のループで全要素を順番に処理する方法 |
| 集計の向きの違い | 行ごとの集計と列ごとの集計を切り替える考え方 |
2次元配列は最初こそ少し複雑に感じますが、表をそのままプログラムで扱える便利な仕組みです。
そして、その表を自在に処理するための基本が二重ループです。
今回のように、まずは「行ごとに見る」「列ごとに見る」という2つの見方をしっかり押さえておくと、この先の表計算的な処理や、少し複雑なデータ処理にもつながっていきます。
