第15章の4 構造体と関数

第11章 関数」では、「11-2. 関数間のデータ授受の方法」と「11-3. 関数の戻り値を返す方法」で 関数どうしのデータのやり取りについて説明しましたが、構造体も関数の引数や戻り値に指定して関数間で受け渡すことができます。

1. 関数に構造体を渡す

構造体も通常の変数のように「値渡し」と「アドレス渡し」の2つの方法で引数に指定することができます。

(1) 値渡し

引数の独立性が保たれ安全なプログラムになりますが、対象となる構造体の内容をすべてコピーするため、大きな構造体では無駄な時間が多くなります

#include <stdio.h>

// 構造体の宣言
struct seiseki {      // 成績データ
    int no;           // 学生番号
    char name[20];    // 名前
    double avg;       // 平均点
};

void seidesp1(struct seiseki sei); // 関数プロトタイプ宣言

int main(void)
{
    struct seiseki seito1 = {1001, "中田太郎", 76.5};

    seidesp1(seito1); // 関数の実引数は値
    
    return 0;
}

void seidesp1(struct seiseki sei) // 関数の仮引数は構造体
{
    // ドット演算子(.)でメンバを指定
    printf("%d %s %.1f\n", sei.no, sei.name, sei.avg);
}

【実行結果例】
1001 中田太郎 76.5

(2) アドレス渡し

構造体をコピーすることなく、構造体のアドレスを引数で渡すため、サイズの大きな構造体を渡すときには値渡しより効率的です。

#include <stdio.h>

// 構造体の宣言
struct seiseki {      // 成績データ
    int no;           // 学生番号
    char name[20];    // 名前
    double avg;       // 平均点
};

void seidesp2(struct seiseki *sei); // 関数プロトタイプ宣言

int main(void)
{
    struct seiseki seito2[] = {
        {1001, "中田太郎", 76.5},
        {1002, "橋本雄太", 65.2},
        {1003, "綿引治", 82.8},
        {0, "", 0.0}};

    seidesp2(seito2); // 関数の実引数はアドレス
    
    return 0;
}

void seidesp2(struct seiseki *sei) // 関数の仮引数はポインタ
{
    // アロー演算子(->)でメンバを指定
    while (sei->no != 0) {
        printf("%d %s %.1f\n", sei->no, sei->name, sei->avg);
        sei++;
    }
}

【実行結果例】
1001 中田太郎 76.5
1002 橋本雄太 65.2
1003 綿引治 82.8

〇 演習問題

キーボードから「体重か身長に整数値以外」が入力されるまで、氏名、体重、身長を構造体配列に入力し、入力を終了すると配列の内容を出力するプログラムを作成しなさい。

ただし、氏名、体重、身長を構造体配列に入力する部分は main関数で行い、結果を出力する部分は別関数で行いなさい。

【実行結果例】 氏名、体重、身長を入力しなさい。(終了条件:体重か身長に整数値以外) TANAKA 57.9 174.2 SASAKI 76.9 171.6 SIBUSAWA 66.6 165.4 AKASAKA 65.7 186.3 a a a 氏名         体重  身長 TANAKA 57.9  174.2 SASAKI 76.9  171.6 SIBUSAWA 66.6  165.4 AKASAKA 65.7  186.3

水色字はキーボードからの入力

解答例

#include <stdio.h>

#define NINZUU 20        // 人数

struct list {
    char name[20];
    double taijyu;
    double sincyou;
};

void print_dt(struct list *sp);

int main(void)
{
    struct list sintai[NINZUU+1];
    int i = 0;

    printf("氏名、体重、身長を入力しなさい。(終了条件:体重か身長に整数値以外)\n");
    while((scanf("%19s %lf %lf", sintai[i].name, &sintai[i].taijyu, 
        &sintai[i].sincyou) == 3) && i < NINZUU ) {
        i++;
    }
    sintai[i].taijyu = -1;     // ストッパーの設定
    sintai[i].sincyou = -1;     // ストッパーの設定

    print_dt(sintai);     // データ表示

    return 0;
}

// データ表示処理
void print_dt(struct list *sp)
{
    printf("氏名         体重  身長\n");
    while( sp->taijyu != -1 && sp->sincyou != -1 ) { // ストッパーまでループ
        printf("%-20s %5.1f  %5.1f\n", sp->name, sp->taijyu, sp->sincyou);
        sp++;
    }
}

2. 関数から構造体を受け取る

通常の処理と同じように return文を使用して関数から構造体を返却します。
このとき、リターン型は構造体となるので注意してください。

#include <stdio.h>
#include <string.h>    // strncpy関数を使用するために必要

// 構造体の宣言
struct seiseki {      // 成績データ
    int no;           // 学生番号
    char name[20];    // 名前
    double avg;       // 平均点
};

struct seiseki seiset(void); // 関数プロトタイプ宣言

int main(void)
{
    struct seiseki sei;

    sei = seiset(); // 返却値で構造体の変数seiに値をもらう

    printf("%d %s %.1f\n", sei.no, sei.name, sei.avg);
    
    return 0;
}

struct seiseki seiset(void) // 返却値型は構造体seiseki
{
    struct seiseki s;

    s.no = 1001;
    // 配列サイズを考慮してコピー
    strncpy(s.name, "中田太郎", sizeof(s.name) - 1);
    s.avg = 76.5;

    return s;    // 構造体の変数sを返却
}

【実行結果例】
1001 中田太郎 76.5

上記以外に、通常の変数のときと同じように、関数を呼び出すときに構造体をアドレス渡しして、その渡されたアドレスのデータを直接更新することもできます。
これは、上記 2. アドレス渡しで渡された構造体のアドレスを使って、直接構造体の中身を書きかえるやり方です。

〇 演習問題

次の手順に従ってプログラムを作成しなさい。

  1. main()関数の処理
    1. 以下の11個の点数を配列として持つ。 但し、最後の-1はストッパーとする。
    2. この配列を「成績データ取得関数」にアドレス渡しする。
    3. 「成績データ取得関数」より、次の構造体を返却値にして、最高点、最低点、平均点を得る。
    4. 最高点、最低点、平均点を表示する。
  2. 成績データ取得関数の処理
    1. 引数で渡された点数が -1 になるまでループし、最高点、最低点、合計点、点数総数を求める。
    2. 合計点と点数総数から平均点を求める。
    3. 最高点、最低点、平均点を上記 1.3 と同じ型の構造体にセットして返却する。

【実行結果例】
最高点 = 94 最低点 = 37 平均点 = 66.2

解答例

#include <stdio.h>

struct s_data { // 成績データ
    int max;       // 最高点
    int min;       // 最低点
    double avg;    // 平均点
};

struct s_data seiset(int *p);

int main(void)
{
    struct s_data seiseki;
    int ten[] = {78, 86, 56, 77, 47, 63, 94, 37, 50, 74, -1};

    seiseki = seiset(ten);    // 成績データの取得

    printf("最高点 = %d\n", seiseki.max);
    printf("最低点 = %d\n", seiseki.min);
    printf("平均点 = %4.1f\n", seiseki.avg);

    return 0;
}

// 成績データの取得関数
struct s_data seiset(int *p)
{
    struct s_data ret;
    int cnt = 0, goukei = 0;

    ret.max = *p;
    ret.min = *p;
    while(*p != -1) {
        if(ret.max < *p)
            ret.max = *p;
        if(ret.min > *p)
            ret.min = *p;
        goukei += *p;
        cnt++;
        p++;
    }
    ret.avg = ( double ) goukei / cnt;

    return ret;
}

コメント