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

ここまでで「関数は部品」「依存しすぎる関数は再利用しにくい」という話をしてきましたよね。
その流れで次に出てくるのが 配列を関数に渡す(受け渡す) というテーマです。

C言語では、配列を関数に渡すときにちょっと独特なルールがあります。

  • 配列そのものを丸ごとコピーして渡す、というより
    配列の先頭を指す情報(先頭アドレス)が渡る という振る舞いになります(※理由は後でしっかり説明します)
  • だからこそ、関数側は 要素数を別に受け取る のが基本です

この「配列+要素数をセットで渡す」型を身につけると、どんな配列にも使える 汎用関数 が作れるようになります。

サンプルプログラム

ここでは「1週間(7日)の歩数」と「1週間(7日)の睡眠時間」を入力して、それぞれの最大値を求めるプログラムを例に解説をします。

サンプル:2種類の配列を同じ関数で処理する

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

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 2つの配列(歩数・睡眠時間)の最大値を求める

#include <stdio.h>

#define DAYS 7   // 1週間

//--- 要素数nの配列vの最大値を返す ---//
int max_of(const int v[], int n)
{
    int max = v[0];

    for (int i = 1; i < n; i++) {
        if (v[i] > max)
            max = v[i];
    }
    return max;
}

int main(void)
{
    int steps[DAYS];   // 歩数
    int sleep[DAYS];   // 睡眠時間(分)

    puts("1週間のデータを入力してください。");
    for (int i = 0; i < DAYS; i++) {
        printf("%d日目 歩数:", i + 1);
        scanf("%d", &steps[i]);
        printf("     睡眠(分):");
        scanf("%d", &sleep[i]);
    }

    int max_steps = max_of(steps, DAYS);
    int max_sleep = max_of(sleep, DAYS);

    printf("歩数の最大=%d\n", max_steps);
    printf("睡眠(分)の最大=%d\n", max_sleep);

    return 0;
}

実行例

1週間のデータを入力してください。
1日目 歩数:6500
     睡眠(分):420
2日目 歩数:7200
     睡眠(分):360
3日目 歩数:5000
     睡眠(分):480
4日目 歩数:9100
     睡眠(分):390
5日目 歩数:8300
     睡眠(分):450
6日目 歩数:10000
     睡眠(分):300
7日目 歩数:7600
     睡眠(分):510
歩数の最大=10000
睡眠(分)の最大=510

配列を渡すときの「見た目」と「実際」

ここがいちばん大事な感覚です。
コード上は「配列を渡している」ように見えるのに、実際は「先頭を指す情報を渡している」挙動になります。

配列の受渡しで起きていること

観点呼び出し側呼び出され側どういう意味?
書き方max_of(steps, DAYS)int max_of(const int v[], int n)配列名だけ渡す/関数側は [] で受け取る。
実体steps は int の配列v は配列に見えるが…実際には先頭要素を指す情報として扱われる。」
要素数DAYS を別引数で渡すn として受け取る配列の長さは自動で分からないので必須

図:呼び出し側と関数側の対応

下の図は、関数 max_of を呼び出した瞬間のイメージです。

  • v[0] は steps[0] と同じ
  • v[5] は steps[5] と同じ
    つまり 関数内の v は、渡された配列そのものを指している ように振る舞います。

なぜ要素数を別で渡す必要があるの?

C言語の関数は、受け取った配列 v から「何個あるか」を自動では知れません。
なので、必ず自分で n を渡してあげるのが基本になります。

要素数を渡さないと困ること

困りごと具体的に何が起きる?
どこまで読めばいいか分からないループの上限が決められない
間違って範囲外へアクセスしやすい未定義動作の原因になりやすい
汎用関数にならない使うたびに固定長前提になる

関数頭部の読み方(書式と意味)

今回のキモはここです。

max_of の書式

  • 返却値型:int
  • 関数名:max_of
  • 仮引数:const int v[] と int n
int max_of(const int v[], int n)

関数頭部のパーツ分解

パーツ意味
返却値型int最大値を int で返す
関数名max_of「最大値を求める」役割の名前
配列仮引数const int v[]int の配列を受け取る(読み取り専用にする)
要素数int n配列の要素数を別で受け取る

const を付ける理由

max_of は「調べるだけ」で、配列の中身を変更しません。
なので const を付けておくと、「うっかり書き換え」をコンパイラが防いでくれて安全です。

この関数が「汎用的」になる理由(依存しない設計)

今回の max_of は、

  • 特定の配列名に依存しない(stepsでもsleepでもOK)
  • 固定の人数や日数に依存しない(7でも30でもOK)
  • 外のマクロやグローバル変数に頼らない

つまり「部品」として完成度が高いです。

依存していないポイント

依存対象依存していると…今回はどう?
グローバル配列別データで使いにくい引数で受け取るのでOK
固定要素数サイズ変更に弱いn を引数で受け取るのでOK
入出力テストしにくいmax_of は計算だけでOK

演習:配列の最小値を求めよう

「1週間の気温(7個)を入力して、最小値を表示する」プログラムを作成せよ。
最小値を求める関数 min_of を作って、main から呼び出すこと。

  • 関数:int min_of(const int v[], int n);

解答例

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

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define DAYS 7

int min_of(const int v[], int n)
{
    int min = v[0];
    for (int i = 1; i < n; i++) {
        if (v[i] < min)
            min = v[i];
    }
    return min;
}

int main(void)
{
    int temp[DAYS];

    puts("1週間の気温を入力してください。");
    for (int i = 0; i < DAYS; i++) {
        printf("%d日目:", i + 1);
        scanf("%d", &temp[i]);
    }

    printf("最小の気温=%d\n", min_of(temp, DAYS));

    return 0;
}

解説(ポイントだけ)

  • min_of は配列 temp を直接知らない。引数 v と n だけで動く。
  • だから別の配列(湿度、売上、点数)でも同じ関数を使える。
  • const を付けて「読むだけ」を明確化している。

重要ポイント(ここだけは覚えておく)

  • 配列を関数に渡すときは、配列名だけ を渡す。
  • 関数側は 型 引数名[] の形で受け取る。
  • 要素数は別引数で渡す(超重要)
  • 読むだけなら const を付けると安全