C言語基礎|多次元配列の受渡し

ここまでは、配列を関数に渡す基本(配列+要素数)をやりましたね。
ここから一歩進んで、2次元配列(表)や、さらに上の多次元配列を関数に渡す方法を学びます。

多次元配列は、見た目は「表」ですが、関数に渡すときには 配列の形(行数・列数) がとても大事になります。
特にC言語では、2次元配列を受け取る仮引数には“列数”が必要というルールがあり、ここを押さえると一気にスッキリ理解できます。

今回の例:4人×3科目の「2回分テスト」を合計して表示

  • testA:1回目のテスト点
  • testB:2回目のテスト点
  • total:合計点

サンプルプログラム

プロジェクト名:chap6-18-1 ソースファイル名:chap6-18-1.c

// 4人×3科目のテスト2回分の合計を求めて表示(関数版)

#include <stdio.h>

#define STUDENT 4
#define SUBJECT 3

//--- STUDENT行SUBJECT列の表 a と b の和を out に格納する ---//
void score_add(const int a[STUDENT][SUBJECT], const int b[STUDENT][SUBJECT],
               int out[STUDENT][SUBJECT])
{
    for (int i = 0; i < STUDENT; i++)
        for (int j = 0; j < SUBJECT; j++)
            out[i][j] = a[i][j] + b[i][j];
}

//--- STUDENT行SUBJECT列の表 m を表示する ---//
void score_print(const int m[STUDENT][SUBJECT])
{
    for (int i = 0; i < STUDENT; i++) {
        for (int j = 0; j < SUBJECT; j++)
            printf("%4d", m[i][j]);
        putchar('\n');
    }
}

int main(void)
{
    int testA[STUDENT][SUBJECT] = {
        {72, 88, 91},
        {65, 70, 68},
        {90, 77, 84},
        {55, 60, 73}
    };
    int testB[STUDENT][SUBJECT] = {
        {80, 85, 89},
        {70, 76, 74},
        {88, 79, 90},
        {60, 66, 71}
    };
    int total[STUDENT][SUBJECT];

    score_add(testA, testB, total);

    puts("【1回目のテスト】");
    score_print(testA);

    puts("【2回目のテスト】");
    score_print(testB);

    puts("【2回分の合計】");
    score_print(total);

    return 0;
}

図でつかむ:2次元配列は「表」をそのまま渡す

2次元配列は「4人×3科目」の表です。
関数へ渡すと、関数側でも同じ形の表として扱えます。

図:データの流れ(イメージ)

なぜ列数が必要?(多次元配列の受渡しの核心)

2次元配列 m[i][j] を扱うとき、C言語は内部的に

  • 「1行が何個の要素でできているか(列数)」

を使って、次の行へ移動する計算をします。

だから、関数側の仮引数は

  • 行数は省略できることが多い
  • 列数は省略できない(計算できなくなる)

というルールになります。

2次元配列の仮引数で重要なこと

観点どうなる?理由
行数(例:4)場合によっては省略可能呼び出し側の配列に合わせて扱えることがある
列数(例:3)省略しにくい(基本は必要)m[i][j] の位置計算に必須

※この教材では、分かりやすさ優先で 行数・列数とも固定にしてあります。まずはここから入るのが安心です。

const を付ける意味(安心して渡せる)

今回、

  • score_add は a と b を読み取るだけ(書き換えない)
  • score_print も読み取るだけ(書き換えない)
  • out は結果を書き込む

という役割でした。

const を付ける判断

引数中で書き換える?const を付ける?
aいいえ付ける
bいいえ付ける
outはい付けない
m(表示用)いいえ付ける

const を付けておくと、うっかり代入してしまったときにコンパイラが止めてくれるので、安全のガードになります。

使った命令の書式と役割(今回出てきたもの)

登場する命令まとめ

命令書式何をする命令?
#include#include <stdio.h>printf / puts / putchar など入出力の宣言を取り込む
#define#define 名前 値定数のように使えるマクロを定義する
forfor (初期化; 条件; 更新) 文繰り返し処理を行う(行・列で2重ループ)
putsputs(文字列);文字列を表示して改行する
printfprintf(書式, …);書式付きで表示する
putcharputchar(文字);1文字だけ表示する(ここでは改行)
returnreturn 値;関数を終了して呼び出し元へ戻る

演習問題

演習6-12:2回分の点数の「差」を求める関数

4人×3科目の表 a と b について、各要素の差(b - a)を out に入れる関数を作れ。

void score_sub(const int a[4][3], const int b[4][3], int out[4][3]);

解答例

void score_sub(const int a[4][3], const int b[4][3], int out[4][3])
{
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 3; j++)
            out[i][j] = b[i][j] - a[i][j];
}

解説
足し算と同じ形で書けます。2重ループで全要素をなめて、差を out に代入するだけです。a と b は読み取りだけなので const 付きが安心です。

演習6-13:指定した学生1人分(1行)の合計を返す関数

4人×3科目の表 t について、指定した学生 row(0〜3)の3科目合計を返す関数を作れ。

int row_sum(const int t[4][3], int row);

解答例

int row_sum(const int t[4][3], int row)
{
    if (row < 0 || row >= 4)
        return 0;

    int sum = 0;
    for (int j = 0; j < 3; j++)
        sum += t[row][j];

    return sum;
}

解説
2次元配列は「行を固定して列を回す」書き方ができるのが便利です。範囲外の row が来たときに備えて、最初にチェックしておくと安全です。