構造体
構造体はカスタム用紙のようなものです。異なる種類の情報(名前、年齢、成績)をひとまとめにパッケージし、全体として管理できるようにします。
構造体の定義と宣言
構造体型の定義
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 閉じ括弧の直後に変数宣言を続けられるためです。セミコロンは宣言リストの終わりをコンパイラに伝えます。省略すると構文エラーになります。
📖 まとめ
structは異なる型のメンバをカスタム型にパッケージします- 初期化には順序付きの値や、C99の指示付き初期化子
.name = ...が使えます - 変数には
.で、ポインタには->でメンバにアクセスします - 構造体配列で同じ型のデータを一括管理できます
- 関数の引数には構造体ポインタを渡すことでコピーオーバーヘッドを避けてください
- 構造体は入れ子にできます。メンバ自体を構造体にできます
📝 練習問題
- 幅と高さを持つ
struct Rectangleを定義し、関数float area(struct Rectangle *r)とfloat perimeter(struct Rectangle *r)を作成して、main関数で計算結果を出力してください struct Dateを定義し、関数int date_compare(struct Date *a, struct Date *b)を作成してください。aがbより早いなら-1、等しければ0、遅いなら1を返します- 学生構造体配列(5人)を定義し、成績の高い順にソートして結果を出力してください



