構造体

構造体はカスタム用紙のようなものです。異なる種類の情報(名前、年齢、成績)をひとまとめにパッケージし、全体として管理できるようにします。

構造体の定義と宣言

構造体型の定義

C
struct Student {
    char name[20];
    int age;
    float score;
};

これは struct Student という型を定義します。末尾のセミコロンは必須です。

構造体変数の宣言

C
struct Student s1;
struct Student s2, s3;

型の定義と同時に変数を宣言することもできます:

C
struct Point {
    int x;
    int y;
} p1, p2;

無名構造体

一度きりしか使わない場合、型名を省略できます:

C
struct {
    int width;
    int height;
} box;
⚠️ 注意: 無名構造体は後から新しい変数を宣言できません。通常は一度きりの用途にのみ使われます。

構造体変数の初期化

順序付き初期化

C
struct Student s1 = {"Zhang", 20, 89.5};

指示付き初期化子(C99)

C
struct Student s2 = {.name = "Li", .score = 92.0, .age = 21};
💡 ヒント: 指示付き初期化子は宣言順に従う必要がありません。指定されなかったメンバは自動的に0で初期化されます。

部分初期化

C
struct Student s3 = {"Wang"};

name が初期化され、残りのメンバは自動的に0に設定されます。

メンバアクセス

ドット演算子 .

構造体変数を通じてメンバにアクセスします:

C
struct Student s = {"Zhao", 19, 78.5};
printf("Name: %s\n", s.name);
printf("Age: %d\n", s.age);
s.score = 85.0;

アロー演算子 ->

構造体ポインタを通じてメンバにアクセスします:

C
struct Student s = {"Zhao", 19, 78.5};
struct Student *ps = &s;
printf("Name: %s\n", ps->name);
ps->age = 20;

ps->name(*ps).name と等価です。-> 演算子は構文糖であり、ポインタを使う際により簡潔に書けます。

💡 ヒント: 覚え方:変数にはドット、ポインタにはアローを使います。

C
#include <stdio.h>

struct Book {
    char title[50];
    char author[30];
    float price;
};

int main(void) {
    struct Book b1 = {.title = "The C Programming Language", .author = "K&R", .price = 45.0};
    struct Book *pb = &b1;

    printf("Title: %s\n", pb->title);
    printf("Author: %s\n", pb->author);
    printf("Price: %.1f\n", pb->price);

    pb->price = 55.0;
    printf("After discount: %.1f\n", b1.price);
    return 0;
}
▶ 試してみよう
TEXT
Title: The C Programming Language
Author: K&R
Price: 45.0
After discount: 55.0

構造体配列

複数の構造体を配列に配置して一括管理します。

C
struct Student class[3] = {
    {"Zhang", 20, 89.5},
    {"Li", 21, 92.0},
    {"Wang", 19, 78.0}
};

for (int i = 0; i < 3; i++) {
    printf("%s %d %.1f\n", class[i].name, class[i].age, class[i].score);
}
TEXT
Zhang 20 89.5
Li 21 92.0
Wang 19 78.0

ポインタで構造体配列を走査します:

C
struct Student *p = class;
for (int i = 0; i < 3; i++, p++) {
    printf("%s %.1f\n", p->name, p->score);
}

構造体ポインタ

構造体ポインタの最も一般的な用途:関数間で構造体全体をコピーするのを避けることです。

C
void print_student(struct Student *ps) {
    printf("%s, age %d, score %.1f\n", ps->name, ps->age, ps->score);
}

int main(void) {
    struct Student s = {"Zhang", 20, 89.5};
    print_student(&s);
    return 0;
}
TEXT
Zhang, age 20, score 89.5
⚠️ 注意: 値渡しでは構造体全体がコピーされます。構造体のメンバが多い場合や大きな配列を含む場合、オーバーヘッドが大きくなります。ポインタ渡しではアドレス1つ分のコピーで済みます。

構造体の関数引数渡し

値渡し

C
void add_score(struct Student s, float bonus) {
    s.score += bonus;
}
⚠️ 注意: 値渡しの場合、関数内の変更はコピーに影響するだけで、元の変数は変更されません。

ポインタ渡し

C
void add_score(struct Student *s, float bonus) {
    s->score += bonus;
}

関数内でポインタを通じた変更は、元の変数に直接反映されます。

C
#include <stdio.h>

struct Score {
    char name[20];
    int chinese;
    int math;
    int english;
};

int total(struct Score *s) {
    return s->chinese + s->math + s->english;
}

void boost(struct Score *s, int bonus) {
    s->chinese += bonus;
    s->math += bonus;
    s->english += bonus;
}

int main(void) {
    struct Score stu = {.name = "Zhou", .chinese = 80, .math = 75, .english = 88};
    printf("Original total: %d\n", total(&stu));
    boost(&stu, 5);
    printf("After bonus: %d\n", total(&stu));
    return 0;
}
▶ 試してみよう
TEXT
Original total: 243
After bonus: 258

構造体の代入と比較

代入

同じ型の構造体は直接代入できます。メンバが一つずつコピーされます:

C
struct Student s1 = {"Zhang", 20, 89.5};
struct Student s2;
s2 = s1;

比較

⚠️ 注意: 構造体は ==!= で比較できません。メンバを個別に比較する必要があります:

C
int equal(struct Student *a, struct Student *b) {
    return a->age == b->age && a->score == b->score;
}

入れ子構造体

構造体のメンバ自体を別の構造体にできます:

C
struct Date {
    int year;
    int month;
    int day;
};

struct Employee {
    char name[20];
    struct Date birthday;
    float salary;
};

struct Employee e = {"Chen", {1995, 6, 15}, 8000.0};
printf("Birthday: %d-%02d-%02d\n", e.birthday.year, e.birthday.month, e.birthday.day);
TEXT
Birthday: 1995-06-15

❓ よくある質問

Q .-> の違いは何ですか?
A . は構造体変数を通じてメンバにアクセスし、-> は構造体ポインタを通じてメンバにアクセスします。p->name(*p).name と等価です。
Q 構造体は == で比較できますか?
A いいえ。C言語は構造体の == 比較をサポートしていません。メンバを個別に比較するか、memcmpを使う必要があります(ただしパディングバイトに注意してください)。
Q 構造体は値渡しとポインタ渡しのどちらがよいですか?
A 一般的にはポインタ渡しが効率的で、元のデータも変更できます。読み取り専用の状況ではconst保護を追加してください:const struct Student *s
Q なぜ構造体の定義の末尾にセミコロンが必要ですか?
A 閉じ括弧の直後に変数宣言を続けられるためです。セミコロンは宣言リストの終わりをコンパイラに伝えます。省略すると構文エラーになります。

📖 まとめ

📝 練習問題

  1. 幅と高さを持つ struct Rectangle を定義し、関数 float area(struct Rectangle *r)float perimeter(struct Rectangle *r) を作成して、main関数で計算結果を出力してください
  2. struct Date を定義し、関数 int date_compare(struct Date *a, struct Date *b) を作成してください。aがbより早いなら-1、等しければ0、遅いなら1を返します
  3. 学生構造体配列(5人)を定義し、成績の高い順にソートして結果を出力してください
100%