
C言語の基本|2次元配列と二重ループ
二重ループの向きを変えれば、表データの見え方も変わる。行と列を自在に集計しよう。
2次元配列は、行と列を持つ表形式のデータを管理するための仕組みです。複数の機械について数日分の生産数を記録したり、学生ごとの科目点数を保存したりするときに便利です。
ただし、2次元配列のすべての要素を処理するには、行と列の両方を順番に動かす必要があります。そこで使うのが、for文を2つ組み合わせた二重ループです。
外側のfor文で行を進め、内側のfor文で列を進めれば、表を横方向に処理できます。反対に、外側で列、内側で行を進めると、縦方向の集計ができます。
このループの向きの違いが分かると、次のような処理を自然に書けるようになります。
- 行ごとの合計と平均を求める
- 列ごとの合計と平均を求める
- 2次元配列のすべての要素を表示する
- 行と列の意味に合わせて集計方向を切り替える
ここでは、3台の機械について4日分の生産数を管理し、機械ごと、日ごとの合計と平均を求めるプログラムを通して、2次元配列と二重ループの関係を詳しく見ていきます。
2次元配列を表として考える
2次元配列は、行と列を持つ表として考えると分かりやすくなります。
今回扱う生産数は、次のようなデータです。
| 機械\日 | 1日目 | 2日目 | 3日目 | 4日目 |
|---|---|---|---|---|
| 機械1 | 24 | 30 | 27 | 33 |
| 機械2 | 20 | 28 | 25 | 31 |
| 機械3 | 26 | 32 | 29 | 35 |
この表は、次の2次元配列で表せます。
int production[3][4] = {
{24, 30, 27, 33},
{20, 28, 25, 31},
{26, 32, 29, 35}
};配列の宣言にある3が行数、4が列数です。
3行 × 4列 = 12要素今回の配列では、行と列に次の意味を持たせています。
| 方向 | データ上の意味 |
|---|---|
| 行 | 機械 |
| 列 | 日 |
| production[行][列] | 指定した機械の指定日の生産数 |
行と列の添字を確認しよう
2次元配列では、2つの添字を使って要素を指定します。
production[行の添字][列の添字]C言語の添字は0から始まります。
| 要素 | 意味 | 値 |
|---|---|---|
| production[0][0] | 機械1の1日目 | 24 |
| production[0][2] | 機械1の3日目 | 27 |
| production[1][1] | 機械2の2日目 | 28 |
| production[2][3] | 機械3の4日目 | 35 |
人から見た機械1は行の添字0、1日目は列の添字0です。
行数が3なので、使用できる行の添字は0から2までです。列数が4なので、使用できる列の添字は0から3までです。
図1:行と列で見る2次元配列

この図から分かること
最初の添字は行、2番目の添字は列を指定します。
今回の配列では、行が機械、列が日を表します。たとえばproduction[1][2]は行1、列2なので、人から見れば機械2の3日目の生産数です。
二重ループが必要になる理由
1次元配列は1つの添字で要素を指定するため、1つのfor文で全要素を処理できます。
for (int i = 0; i < 要素数; i++) {
array[i]を処理する;
}2次元配列には行と列があるため、両方の添字を動かす必要があります。
for (int row = 0; row < 行数; row++) {
for (int col = 0; col < 列数; col++) {
array[row][col]を処理する;
}
}それぞれのfor文には、次の役割があります。
| ループ | 役割 |
|---|---|
| 外側のfor文 | 処理する行を選ぶ |
| 内側のfor文 | 選んだ行の列を順番に処理する |
外側のrowが0の間に、内側のcolは0から最後の列まで変化します。
row = 0
col = 0
col = 1
col = 2
col = 3
row = 1
col = 0
col = 1
col = 2
col = 3
row = 2
col = 0
col = 1
col = 2
col = 3この動きによって、3行4列の12要素をすべて処理できます。
行方向に集計する考え方
機械ごとの合計を求める場合は、1台の機械を固定して、その機械の4日分を横方向に足します。
機械1なら、次の4要素を加算します。
production[0][0]
production[0][1]
production[0][2]
production[0][3]計算結果は次のとおりです。
24 + 30 + 27 + 33 = 114二重ループでは、外側で機械を固定し、内側で日を動かします。
for (int machine = 0; machine < MACHINE_COUNT; machine++) {
for (int day = 0; day < DAY_COUNT; day++) {
machine_totals[machine] += production[machine][day];
}
}外側のループ変数machineは機械、内側のループ変数dayは日を表します。
列方向に集計する考え方
日ごとの合計を求める場合は、1日を固定して、その日に記録された3台分の値を縦方向に足します。
1日目なら、次の3要素を加算します。
production[0][0]
production[1][0]
production[2][0]計算結果は次のとおりです。
24 + 20 + 26 = 70今度は外側で日を固定し、内側で機械を動かします。
for (int day = 0; day < DAY_COUNT; day++) {
for (int machine = 0;
machine < MACHINE_COUNT;
machine++) {
day_totals[day] += production[machine][day];
}
}同じ2次元配列を使用していても、外側と内側のループを入れ替えることで、集計する方向が変わります。
| 外側のループ | 内側のループ | 集計する方向 |
|---|---|---|
| 機械 | 日 | 行方向・機械ごと |
| 日 | 機械 | 列方向・日ごと |
機械ごと・日ごとの生産数を集計する
3台の機械について生産数の合計と平均を求めるプログラム
ファイル名:10_9_1.c
#include <stdio.h>
#define MACHINE_COUNT 3
#define DAY_COUNT 4
int main(void)
{
/* 3台の機械について4日分の生産数を管理する */
int production[MACHINE_COUNT][DAY_COUNT] = {
{24, 30, 27, 33},
{20, 28, 25, 31},
{26, 32, 29, 35}
};
/* 機械ごとの集計結果を保存する */
int machine_totals[MACHINE_COUNT] = {0};
double machine_averages[MACHINE_COUNT] = {0.0};
/* 機械ごとの合計と平均を求める */
for (int machine = 0;
machine < MACHINE_COUNT;
machine++) {
for (int day = 0; day < DAY_COUNT; day++) {
machine_totals[machine] +=
production[machine][day];
}
machine_averages[machine] =
(double)machine_totals[machine] / DAY_COUNT;
}
/* 機械ごとの結果を表示する */
printf("機械ごとの合計生産数と平均生産数\n");
for (int machine = 0;
machine < MACHINE_COUNT;
machine++) {
printf("機械%d:合計=%d、平均=%.2f\n",
machine + 1,
machine_totals[machine],
machine_averages[machine]);
}
/* 日ごとの集計結果を保存する */
int day_totals[DAY_COUNT] = {0};
double day_averages[DAY_COUNT] = {0.0};
/* 日ごとの合計と平均を求める */
for (int day = 0; day < DAY_COUNT; day++) {
for (int machine = 0;
machine < MACHINE_COUNT;
machine++) {
day_totals[day] +=
production[machine][day];
}
day_averages[day] =
(double)day_totals[day] / MACHINE_COUNT;
}
/* 日ごとの結果を表示する */
printf("\n日ごとの合計生産数と平均生産数\n");
for (int day = 0; day < DAY_COUNT; day++) {
printf("%d日目:合計=%d、平均=%.2f\n",
day + 1,
day_totals[day],
day_averages[day]);
}
return 0;
}実行結果の例
機械ごとの合計生産数と平均生産数
機械1:合計=114、平均=28.50
機械2:合計=104、平均=26.00
機械3:合計=122、平均=30.50
日ごとの合計生産数と平均生産数
1日目:合計=70、平均=23.33
2日目:合計=90、平均=30.00
3日目:合計=81、平均=27.00
4日目:合計=99、平均=33.00プログラムの全体像を確認しよう
このプログラムでは、同じproduction配列を2つの方向から集計しています。
| 集計内容 | データを見る方向 |
|---|---|
| 機械ごとの集計 | 行を横方向に見る |
| 日ごとの集計 | 列を縦方向に見る |
2次元配列の中身は変わりません。変わるのは、外側のfor文で何を固定するかです。
機械ごとの合計を求める処理
機械ごとの集計では、外側のfor文で機械を1台ずつ選びます。
for (int machine = 0;
machine < MACHINE_COUNT;
machine++) {内側のfor文では、選んだ機械について4日分の生産数を順番に加算します。
for (int day = 0; day < DAY_COUNT; day++) {
machine_totals[machine] +=
production[machine][day];
}machineが0の場合、次の処理が行われます。
machine_totals[0]へproduction[0][0]を加える
machine_totals[0]へproduction[0][1]を加える
machine_totals[0]へproduction[0][2]を加える
machine_totals[0]へproduction[0][3]を加える具体的な計算は次のとおりです。
0 + 24 + 30 + 27 + 33 = 114結果はmachine_totals[0]へ保存されます。
図2:行方向に機械ごとの値を集計する

この図から分かること
機械ごとの集計では、外側のfor文によって1台の機械を固定します。内側のfor文が、その機械の1日目から4日目までを横方向に処理します。
内側のループが終了した時点で、1台分の合計が完成します。その合計を日数で割れば、機械ごとの平均を求められます。
機械ごとの平均を求める処理
合計を求めたあと、4日分の平均を計算します。
machine_averages[machine] =
(double)machine_totals[machine] / DAY_COUNT;機械1の合計は114なので、計算は次のようになります。
114 ÷ 4 = 28.5平均値には小数部分が含まれる可能性があるため、結果をdouble型の配列へ保存しています。
double machine_averages[MACHINE_COUNT] = {0.0};日ごとの合計を求める処理
日ごとの集計では、外側のfor文で日を1日ずつ選びます。
for (int day = 0; day < DAY_COUNT; day++) {内側のfor文では、選んだ日について3台分の生産数を加算します。
for (int machine = 0;
machine < MACHINE_COUNT;
machine++) {
day_totals[day] +=
production[machine][day];
}dayが0の場合は、1日目の列を処理します。
production[0][0] = 24
production[1][0] = 20
production[2][0] = 26合計は次のようになります。
24 + 20 + 26 = 70結果はday_totals[0]へ保存されます。
日ごとの平均を求める処理
日ごとの合計を求めたあと、機械の台数で割ります。
day_averages[day] =
(double)day_totals[day] / MACHINE_COUNT;1日目の合計は70、機械は3台なので、次の計算になります。
70 ÷ 3 = 23.333...printfで%.2fを指定しているため、表示は小数第2位までの23.33になります。
図3:列方向に日ごとの値を集計する

この図から分かること
日ごとの集計では、外側のfor文によって1日を固定します。内側のfor文は、その日の機械1から機械3までを縦方向に処理します。
同じ2次元配列でも、外側で行を固定するか、列を固定するかによって集計対象が変わります。
ループの向きと集計対象の関係
行方向と列方向の違いを整理すると、次のようになります。
| 求めたい結果 | 外側のループ | 内側のループ | 加算先 |
|---|---|---|---|
| 機械ごとの合計 | machine | day | machine_totals[machine] |
| 日ごとの合計 | day | machine | day_totals[day] |
機械ごとの集計では、machineを固定してdayを動かします。
production[machine][0]
production[machine][1]
production[machine][2]
production[machine][3]日ごとの集計では、dayを固定してmachineを動かします。
production[0][day]
production[1][day]
production[2][day]どちらの添字を固定し、どちらを動かしているかを見ると、二重ループの目的が分かりやすくなります。
{0}で集計用配列を初期化する
合計を保存する配列は、すべての要素を0で初期化しています。
int machine_totals[MACHINE_COUNT] = {0};
int day_totals[DAY_COUNT] = {0};初期化子が要素数より少ない場合、残りの要素も0で初期化されます。
そのため、次の配列は全要素が0になります。
int values[5] = {0};| 要素 | 初期値 |
|---|---|
| values[0] | 0 |
| values[1] | 0 |
| values[2] | 0 |
| values[3] | 0 |
| values[4] | 0 |
合計は0から加算を始める必要があります。不定値が入った状態で加算すると、正しい結果を得られません。
平均値を保存する配列も0.0で初期化しています。
double machine_averages[MACHINE_COUNT] = {0.0};
double day_averages[DAY_COUNT] = {0.0};この場合も、すべての要素が0.0で初期化されます。
平均計算でdoubleへ型変換する理由
次の式では、合計値をdouble型へ変換しています。
(double)machine_totals[machine] / DAY_COUNT整数同士の割り算では、小数部分が切り捨てられます。
| 式 | 計算結果 |
|---|---|
| 70 / 3 | 23 |
| (double)70 / 3 | 23.333333... |
合計値の前に(double)を付けると、計算が浮動小数点数として行われます。これによって、小数部分を含む平均値を求められます。
結果を小数第2位まで表示するときは、printfで%.2fを使用します。
printf("%.2f\n", average);マクロで行数と列数を管理する
サンプルでは、機械数と日数をマクロで定義しています。
#define MACHINE_COUNT 3
#define DAY_COUNT 42次元配列の宣言とfor文の条件で、同じマクロを使用しています。
int production[MACHINE_COUNT][DAY_COUNT];
for (int machine = 0;
machine < MACHINE_COUNT;
machine++) {
for (int day = 0; day < DAY_COUNT; day++) {マクロを使うと、行数や列数の意味が伝わりやすくなります。
| 記述 | 読み取れる意味 |
|---|---|
| production[3][4] | 3行4列 |
| production[MACHINE_COUNT][DAY_COUNT] | 機械数×日数 |
数値を直接何度も書かずに済むため、変更時の修正漏れも防ぎやすくなります。
二重ループを読む3つのポイント
外側のループが何を固定しているか
次の外側ループでは、機械を1台ずつ固定しています。
for (int machine = 0;
machine < MACHINE_COUNT;
machine++) {この内側でdayを動かせば、1台分の全日データを処理できます。
一方、次の外側ループでは日を固定しています。
for (int day = 0; day < DAY_COUNT; day++) {この内側でmachineを動かせば、1日分の全機械データを処理できます。
内側のループが何を動かしているか
内側のループ変数がdayなら、横方向に日を進めています。
内側の変数がmachineなら、縦方向に機械を進めています。
どの配列要素へ加算しているか
次の式は、機械ごとの合計を更新しています。
machine_totals[machine] +=
production[machine][day];次の式は、日ごとの合計を更新しています。
day_totals[day] +=
production[machine][day];右辺のproductionは同じですが、左辺の保存先が異なります。どこへ結果を残しているかを見ると、何を集計しているのか判断できます。
2次元配列と二重ループで注意したいこと
| 注意点 | 確認すること |
|---|---|
| 行と列の意味 | 配列を宣言するときに決めておく |
| 添字の順番 | 最初が行、次が列 |
| 外側のループ | 何を1つずつ固定するのか |
| 内側のループ | 固定した対象の中で何を動かすのか |
| ループ条件 | 行には行数、列には列数を使う |
| 合計用配列 | {0}で全要素を0にする |
| 平均の計算 | doubleへ型変換してから割る |
| 表示する番号 | 人向けの表示には添字へ1を加える |
| マクロ名 | 行数と列数の意味が分かる名前にする |
2次元配列の二重ループは、外側で何を固定し、内側で何を動かすかを考えると読みやすくなります。
行方向へ集計したいなら外側で行を固定し、列方向へ集計したいなら外側で列を固定します。この切り替えができるようになると、同じ表データから異なる視点の合計や平均を求められるようになります。
