C言語基礎|構造体を返す関数

「関数は値を1つしか返せない?…いいえ、構造体なら“ひとまとめ”で返せます!」

C言語の関数って、基本は戻り値が1つですよね。
でも現実の処理では「値を3つまとめて返したい!」みたいな場面、けっこうあります。

たとえば、計算結果として「合計・平均・最大」をまとめて返したい、みたいなケース。
ここで構造体が活躍します。構造体は 代入できる ので、関数の戻り値として 構造体そのもの を返せるんです。

つまり、関数の戻り値を構造体にすると
複数の値をセットにして返せる → コードが素直で読みやすくなる、というわけです。

構造体を返せる理由(配列との対比)

ポイントはここです:構造体は代入できる
だから「返して、受け取って、代入する」が自然にできます。

配列と構造体の違い(戻り値にできる?)

代入関数の戻り値ひとこと
配列できないそのままはできない配列そのものは代入できない
構造体できるできる丸ごとコピー(代入)できる

まずは全体像:構造体を返す流れ

構造体を返して受け取る

(1) 関数の中で構造体 temp を作る
(2) temp に値を詰める
(3) return temp; で丸ごと返す
(4) 呼び出し側で result = func(...); で受け取る

説明

  • return で返ってくるのは「構造体1つ」だけど
  • その中にメンバが複数あるので
  • 実質「複数の値をまとめて返している」状態になります

サンプルプログラム

買い物計算の結果(合計・税込・おつり) を返すプログラム例です。

仕様

  • 単価 price と個数 count と支払い cash を受け取る
  • 合計 total、税込 tax_included、おつり change をまとめて返す
  • 返ってきた構造体を表示する

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

#include <stdio.h>

// 計算結果をまとめる構造体
struct Bill {
    int total;        // 合計(税抜)
    int taxIncluded;  // 税込(10%として計算)
    int change;       // おつり
};

// 合計・税込・おつりをまとめて返す
struct Bill make_bill(int price, int count, int cash)
{
    struct Bill temp;

    temp.total = price * count;
    temp.taxIncluded = temp.total + (temp.total / 10); // 10%の簡易計算
    temp.change = cash - temp.taxIncluded;

    return temp; // 構造体を丸ごと返す
}

int main(void)
{
    struct Bill b;

    b = make_bill(120, 3, 500);

    printf("計算結果です。\n");
    printf("合計(税抜):%d 円\n", b.total);
    printf("合計(税込):%d 円\n", b.taxIncluded);
    printf("おつり:%d 円\n", b.change);

    return 0;
}

登場する命令(要素)の書式と役割を丁寧に解説

構造体の宣言

書式

struct タグ名 {
    型 メンバ名;
    ...
};

何をする?

  • 「複数の値をまとめて扱う型」を作ります。
  • Bill の例では、合計・税込・おつりをひとまとめにしています。

構造体を返す関数

書式

struct 型名 関数名(引数...)
{
    struct 型名 変数;
    ... 値をセット ...
    return 変数;
}

何をする?

  • 関数の中で構造体を作って値を詰め、構造体の値を丸ごと返します
  • 呼び出し側は、受け取った構造体をそのまま変数に代入できます。

return

書式

return 式;

何をする?

  • 関数の実行を終えて、呼び出し元へ値を返します。
  • 今回は return temp; により temp(構造体)全体が返ります。

関数呼び出し式の評価と代入

書式

変数 = 関数名(引数...);

何をする?

  • 関数名(引数...) という式を評価すると「戻り値」が得られます。
  • その戻り値(構造体)が、左辺の構造体変数へ丸ごと代入されます。

図:返ってくる構造体のイメージ

例:make_bill(120, 3, 500) の結果

図の説明

  • 関数は「構造体1つ」を返す。
  • でも構造体の中にはメンバが3つある。
  • だから、まとめて持ち帰れる、という感覚になります。

余談:名前空間(同じ名前が使える理由)

C言語には「名前空間」が複数あり、種類が違えば同じ綴りを使っても衝突しません。

名前空間の4種類

種類どこで使われる?
ラベル名start:goto の飛び先
タグ名struct Xstruct/union/enum のタグ
メンバ名obj.x構造体の中の名前
一般的な識別子変数名、関数名ふだんの名前

同じ x でも種類が違う(イメージ)

struct x {   // タグ名
    int x;   // メンバ名
} x;         // 変数名

x:           // ラベル名
    x.x = 1; // 変数名.メンバ名

説明

  • タグ名の x、メンバ名の x、変数名の x、ラベル名の x は
  • 「所属する名前空間」が違うので同居できます

演習問題

演習12-2(数値3つを読み込んで構造体で返す)

int 型の price と count と cash をキーボードから読み込み、
make_bill と同じ計算結果を持つ struct Bill を返す関数を作ってください。

関数の形:

struct Bill scan_bill(void);

解答例

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

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

#include <stdio.h>

struct Bill {
    int total;
    int taxIncluded;
    int change;
};

struct Bill scan_bill(void)
{
    int price, count, cash;
    struct Bill temp;

    printf("単価を入力:");
    scanf("%d", &price);
    printf("個数を入力:");
    scanf("%d", &count);
    printf("支払い金額を入力:");
    scanf("%d", &cash);

    temp.total = price * count;
    temp.taxIncluded = temp.total + (temp.total / 10);
    temp.change = cash - temp.taxIncluded;

    return temp;
}

int main(void)
{
    struct Bill b = scan_bill();

    printf("入力から計算しました。\n");
    printf("合計(税抜):%d 円\n", b.total);
    printf("合計(税込):%d 円\n", b.taxIncluded);
    printf("おつり:%d 円\n", b.change);

    return 0;
}

解説(ポイント)

  • scan_bill の戻り値は struct Bill
  • 関数内で temp を作り、値を詰めて return temp
  • 呼び出し側は struct Bill b = scan_bill(); のように受け取れる

演習12-3(名前と数値を読み込んで構造体で返す)

学生ではなく、ユーザー設定を返す例に置き換えます。

名前(文字列)とレベル(int)を読み込んで、設定情報をまとめた構造体を返す関数を作ってください。

関数の形:

struct Profile scan_profile(void);

解答例

プロジェクト名:chap12-6-3 ソースファイル名:chap12-6-3.c

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

#include <stdio.h>

#define NAME_LEN 32

struct Profile {
    char name[NAME_LEN];
    int  level;
};

struct Profile scan_profile(void)
{
    struct Profile temp;

    printf("表示名を入力:");
    scanf("%s", temp.name);

    printf("レベルを入力:");
    scanf("%d", &temp.level);

    return temp;
}

int main(void)
{
    struct Profile p = scan_profile();

    printf("登録しました。\n");
    printf("名前:%s\n", p.name);
    printf("レベル:%d\n", p.level);

    return 0;
}

解説(ポイント)

  • name は配列なので scanf で & は不要
  • level は int なので scanf では & が必要
  • まとめて返すので、呼び出し側がスッキリします。