構造体

構造体の概念

構造体とは、複数の値をまとめて管理することができる機能です。
複数のデータ型を寄せ集めて、新しいデータ型を作る機能ともいえます。

配列も複数の値を一括して扱える機能ですが、配列は同じデータ型の集合なのに対して、構造体は異なるデータ型を一括管理できます。

構造体を使ってみる

構造体の定義

実際に構造体を定義したのが以下のサンプルコードです。

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

構造体は以下のような形で定義します。

struct 構造体名
{
    データ型 メンバ変数名;
    データ型 メンバ変数名;
    ...
};

structというのが「これから構造体を定義しますよ」というキーワードです。
structに続き構造体名を記述します。
構造体名は好きに決めて構いません。

次に、波括弧{}でブロックを作ります。
このブロックの中に、構造体で使用したい変数をひとつ以上定義します。
サンプルコードではchar型配列でname(名前)、int型でage(年齢)、char型でgender(性別)を定義し、Person(人物)の情報を格納する構造体を作っています。
nameのように、配列を含めることもできます。

構造体の中で定義した変数の事をメンバ変数(メンバ)と言います。
メンバ変数はいくつでも増やすことができます。

ブロックの最後にはセミコロン(;)を付けるのを忘れないようにしましょう。
関数の定義ではセミコロンが必要なかったので、構造体の定義では忘れてしまいがちです。

構造体を使用する

構造体を実際に使用してみましょう。

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

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

int main()
{
    struct Person person;
    strcpy_s(
        person.name,
        sizeof(person.name) - 1,
        "○山×男");
    person.age = 20;
    person.gender = 0;
    
    printf(
        "name: %s\n"
        "age: %d\n"
        "gender: %d\n",
        person.name, person.age, person.gender);
    getchar();
}
name: ○山×男
age: 20
gender: 0

4~9行目で定義した構造体を、13行目で実際に使用しています。
「struct Person」というのが、最初に定義した構造体を使用するためのキーワードです。
もちろん「Person」の部分は自分でつけた構造体名によって変わります。

構造体はデータ型なので、使用する場合は変数を用意します。
サンプルコードでは「person」という名前で構造体変数を定義しています。
(この場合、頭文字が小文字なので、構造体名とは別の名前と認識されます)

構造体変数からメンバ変数にアクセスするにはドット演算子を使用します。
構造体変数に続いて「.」(ドット、ピリオド)を記述し、さらに使用したいメンバ変数名を記述します。

後は通常の変数と同じように、メンバ変数に値を代入したり取り出したりすることができます。

構造体の初期化

構造体変数は宣言と同時に初期化を行うこともできます。

#include <stdio.h>

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

int main()
{
    struct Person person = { "○山×男", 20, 0 };

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

メンバ変数に文字列が含まれる場合にstrcpyなどの関数を使用する手間がないので、初期化できるならばなるべく初期化した方が楽です。

初期化子は、構造体でメンバ変数を定義した順に記述することに注意してください。
配列の初期化の時と同じく、メンバ変数に対して初期化子が足りない場合は0で埋められます。
以下のようにすればすべての要素を0で初期化した構造体変数が得られます。

struct Person person = { 0 };

これは「初期化子が足りない場合は残りは0で埋める」ことを利用した初期化です。
ここで指定した値ですべて埋められるわけではないので注意してください。

//先頭要素だけが「1」
//残りは「0」
struct Person person = { 1 };

また、この書き方ができるのは宣言と初期化を同時に行う場合のみです。
宣言後の構造体に初期化子を使用することはできません。

struct Person person;

//ダメ
person = { "○山×男", 20, 0 };

//ダメ
person = { 0 };

構造体変数の代入

構造体は配列とは違い、構造体変数に別の構造体変数をそのまま代入することができます。

#include <stdio.h>

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

int main()
{
    struct Person person1 = { "○山×男", 20, 0 };
    struct Person person2;

    person2 = person1;

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

    getchar();
}

構造体変数に別の構造体変数を代入(15行目)すると、構造体の各メンバ変数がすべてコピーされます。

構造体の配列

構造体は配列にして使うこともできます。

#include <stdio.h>

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

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

    int count = sizeof(person) / sizeof(struct Person)
    for (int i = 0; i &lt; count; i++)
    {
        printf(
            "name: %s\n"
            "age: %d\n"
            "gender: %d\n",
            person[i].name, person[i].age, person[i].gender);
    }
    
    getchar();

このように、複数の情報を構造体にしてまとめて、さらに配列にすることで効率的にデータを管理できます。

19行目では構造体配列personの要素数を取得しています。
「sizeof(構造体配列)」とすることで、その構造体配列全体のサイズ(バイト数)が分かります。
「sizeof(struct 構造体名)」とすることで、その構造体一つで必要になるサイズが分かります。

配列全体のサイズを配列ひとつのサイズで割ることで、配列の要素数を得ることができます。

構造体とtypedef

構造体を定義する場合、typedefを使えばより便利になります。

構造体変数を宣言するとき、構造体名の前に「struct」というキーワードを付ける必要があります。
構造体を使用する度に毎回structを記述するのはちょっとした手間です。

typedefを使用することでこれを解決できます。

#include <stdio.h>

//※typedefを使わない場合
//struct Person
//{
//	char name[50];
//	int age;
//	char gender;
//};

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

int main()
{
    //struct Person person = { "○山×男", 20, 0 };
    //↓これがこのように書ける
    Person person = { "○山×男", 20, 0 };

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

4~9行目の構造体の定義を、typedefを使って書き直したのが11~16行目です。

「struct { ~ }」という構造体のデータ型に「Person」という別名を与えることで、構造体を使用する場合にはPersonと記述するだけで済むようになります。

C++では構造体を使用する場合にstructのキーワードを付ける必要がありません。
そのため、このようなtypedefによる置き換えは必要ありません。

Visual StudioはCとC++を混在できるため、typedefを使用せずともstructを省くことができてしまいます。
しかし純粋なC言語ではtypedefを使用しなければstructを省くことはできません。

Visual Studioであっても、ソースコード名の拡張子を「.cpp」から「.c」に変更することでC++機能を排除し、純粋なC言語として扱うことができます。
この場合はtypedefが必要となります。