構造体と関数

引数に構造体を持つ関数

関数の引数には、構造体を指定することもできます。


#include <stdio.h>

typedef struct
{
    char name[50];
    int age;
    char gender;
} Person;

//引数は構造体
void PrintPerson(Person p)
{
    printf("name: %s\n", p.name);
    printf("age: %d\n", p.age);
    printf("gender: %d\n", p.gender);
}

int main()
{
    Person person[] = {
        { "A山B男", 20, 0 },
        { "C下D太", 18, 0 },
        { "E田F子", 21, 1 },
        { "G山H美", 19, 1 },
    };
    PrintPerson(person[2]);

    getchar();
}

特に難しい処理ではないでしょう。

関数の仮引数へは、実引数(呼び出し元)に指定した値をコピーしたものが渡されます。
これは構造体であっても同じです。
そのため、関数内で構造体のメンバ変数を書き換えても、呼び出し元の構造体変数には影響はありません。


#include <stdio.h>

typedef struct
{
    char name[50];
    int age;
    char gender;
} Person;

void PrintPerson(Person p)
{
    p.age = 30;
    printf("name: %s\n", p.name);
    printf("age: %d\n", p.age); //30
    printf("gender: %d\n", p.gender);
}

int main()
{
    Person person = { "A山B男", 20, 0 };
    PrintPerson(person);

    printf("\nage: %d", person.age); //20
    getchar();
}
name: A山B男
age: 30
gender: 0

age: 20

関数内でメンバ変数ageを書き換えていますが、呼び出し元には影響がないことが確認できます。

実引数に複合リテラルを使用する

複合リテラルを実引数に直接記述することも可能です。
これにより、関数呼び出しのためだけに変数を定義する手間が省けます。


Person person = { "A山B男", 20, 0 };
PrintPerson(person);

//↑が↓のように簡潔に書ける

PrintPerson((Person){ "A山B男", 20, 0 });

構造体を返す関数

配列は関数の戻り値にはできませんが、構造体はそれが可能です。


#include <stdio.h>
#include <string.h>

#define NAME_LENGTH 50

typedef struct
{
    char name[NAME_LENGTH];
    int age;
    char gender;
} Person;

Person CreatePerson(const char *name, int age, char gender)
{
    Person p = { "", age, gender };
    strcpy_s(p.name, NAME_LENGTH, name);
    return p;
}

int main()
{
    Person person;
    person = CreatePerson("A山B男", 20, 0);

    printf("name: %s\n", person.name);
    printf("age: %d\n", person.age);
    printf("gender: %d\n", person.gender);

    getchar();
}

構造体は新しいデータ型ですから、関数の戻り値のデータ型としてそのまま記述することができます。
関数内で宣言した構造体変数を戻り値に指定しても問題ありません。
戻り値は構造体変数をコピーしたものが返されます。

関数によって複数の値を同時に返したい場合、引数のポインタ渡しを使用する方法がありましたが、構造体を使えば通常の戻り値として複数の値を受け取ることができます。
ただし、戻り値用の構造体を定義する必要があるため、どちらの方法が良いかはケースバイケースでしょう。

15行目は構造体変数の初期化処理ですが、文字列配列nameの箇所に空文字("")を指定しています。
構造体は、メンバ変数内の文字列配列を文字列リテラルにより初期化することはできます。
しかし文字列ポインタを指定して初期化することはできません。
(これは文字列配列の初期化の制限です)
そのため、いったん空の文字列で初期化し、後からstrcpy(strcpy_s)関数で文字列のコピーを行っています。

また、文字列配列のサイズを#defineによって定義していることに注目してください。

番外:どうしても関数で配列を返したい場合

関数の戻り値に配列を指定することはできません。
関数で配列を得るには、呼び出し元で配列を宣言し、それを引数にして関数内で書き換えるのが一般的です。

どうしても配列を戻り値にしたい場合は構造体を利用する方法もあります。


#include <stdio.h>

typedef struct
{
    int number[10];
} Number10;

Number10 GetNumber10(int start, int step)
{
    Number10 n10;
    for (int i = 0; i < 10; i++)
    {
        n10.number[i] = start + (step * i);
    }

    return n10;
}

int main()
{
    Number10 number10 = GetNumber10(0, 5);

    for (int i = 0; i < 10; i++)
    {
        printf("%d, ", number10.number[i]);
    }

    getchar();
}

配列を受け取るというよりは、そのまま構造体を受け取っているだけです。
わざわざ構造体を定義しなければならず、処理速度の面でもメリットがないためあまり用いられません。