C言語/struct
構造体の基本概念
[編集]構造体は、異なる型のデータ要素を一つの単位としてまとめるためのC言語の機能です。実世界のオブジェクトは通常、複数の属性を持っています。例えば、学生は名前、学籍番号、成績などの属性を持ちます。構造体を使用することで、これらの関連する属性を論理的にグループ化することができます。
構造体の宣言
[編集]構造体の宣言には、structキーワードを使用します。最も基本的な構造体の宣言形式は次の通りです:
struct タグ名 { 型名1 メンバ名1; 型名2 メンバ名2; /* 他のメンバ */ };
具体例を見てみましょう:
struct Student { char name[50]; int id; float gpa; };
この例では、Studentという名前のタグを持つ構造体を宣言しています。この構造体は、文字配列name、整数型id、浮動小数点型gpaという3つのメンバを持ちます。
構造体変数の定義
[編集]構造体を宣言した後、その型の変数を定義することができます:
struct Student s1;
または、宣言と同時に変数を定義することもできます:
struct Student { char name[50]; int id; float gpa; } s1, s2;
C23では、変数定義時にstructキーワードを省略することも可能です(タグ名が型として使用できる場合):
// 以下はC23の機能です typedef struct Student Student; Student s3; // structキーワードなしで使用可能
構造体の初期化
[編集]構造体変数は、次のように初期化できます:
struct Student s1 = {"Tanaka", 12345, 3.8};
C99以降では、指定初期化子(designated initializer)を使用して、特定のメンバだけを初期化することもできます:
struct Student s2 = { .name = "Suzuki", .id = 67890, .gpa = 3.5 };
C23では、入れ子の指定初期化子(nested designated initializers)のサポートが強化されています:
struct Address { char street[100]; char city[50]; }; struct Person { char name[50]; struct Address address; }; struct Person p = { .name = "Yamada", .address = { .street = "123 Main St", .city = "Tokyo" } };
構造体メンバへのアクセス
[編集]構造体のメンバにアクセスするには、ドット演算子(.)を使用します:
s1.id = 54321; printf("Name: %s\n", s1.name);
構造体へのポインタを通じてメンバにアクセスする場合は、アロー演算子(->)を使用します:
struct Student *ptr = &s1; ptr->id = 54321; printf("Name: %s\n", ptr->name);
構造体の配列
[編集]構造体の配列を作成することも可能です:
struct Student class[30]; class[0].id = 10001; strcpy(class[1].name, "Satou");
構造体のネスト
[編集]構造体の中に別の構造体を含めることができます:
struct Date { int day; int month; int year; }; struct Student { char name[50]; int id; struct Date dob; // 生年月日 }; struct Student s3 = {"Watanabe", 13579, {15, 3, 2000}}; printf("Birth Year: %d\n", s3.dob.year);
typedef を使った構造体
[編集]typedefを使用して構造体に別名をつけることで、structキーワードを省略できます:
typedef struct { char name[50]; int id; float gpa; } Student; Student s4; // structキーワードなしで使用可能
または、タグ名を持つ構造体にも別名をつけることができます:
typedef struct Student { char name[50]; int id; float gpa; } Student;
構造体と関数
[編集]構造体は関数に引数として渡したり、関数から返したりすることができます:
Student updateGPA(Student s, float newGPA) { s.gpa = newGPA; return s; } void printStudent(const Student s) { printf("Name: %s, ID: %d, GPA: %.2f\n", s.name, s.id, s.gpa); }
大きな構造体の場合は、パフォーマンス上の理由からポインタを使用することが推奨されます:
void updateStudentGPA(Student *s, float newGPA) { s->gpa = newGPA; }
構造体のメモリ配置
[編集]構造体のメモリ配置は、メンバ宣言の順序に従いますが、コンパイラは最適化のためにパディング(詰め物)を挿入することがあります。次の表は、典型的な32ビットシステムでの構造体メンバのアライメント要件を示しています:
| データ型 | サイズ(バイト) | アライメント要件 |
|---|---|---|
| char | 1 | 1バイト境界 |
| short | 2 | 2バイト境界 |
| int | 4 | 4バイト境界 |
| long | 4 | 4バイト境界 |
| float | 4 | 4バイト境界 |
| double | 8 | 8バイト境界 |
| ポインタ | 4 | 4バイト境界 |
例えば、次の構造体を考えてみましょう:
struct Example { char a; // 1バイト int b; // 4バイト char c; // 1バイト };
この構造体のサイズは12バイトになることがあります(1バイト + 3バイトパディング + 4バイト + 1バイト + 3バイトパディング)。
C11以降では、_Alignas指定子を使用してメンバのアライメントを制御することができます:
struct AlignedExample { char a; _Alignas(8) int b; // 8バイト境界にアライン char c; };
C23では、構造体のビットフィールドでより多くの整数型がサポートされるようになりました。
構造体のビットフィールド
[編集]構造体のメンバには、特定のビット幅を指定することができます。これはビットフィールドと呼ばれます:
struct Flags { unsigned int read : 1; // 1ビット unsigned int write : 1; // 1ビット unsigned int exec : 1; // 1ビット };
C23では、ビットフィールドで使用できる型が拡張され、符号付き型やより広い整数型もサポートされるようになりました:
struct ExtendedFlags { unsigned int read : 1; // 1ビット signed int value : 4; // 4ビット(符号付き) _BitInt(20) data : 20; // 20ビット(C23の拡張型) };
柔軟な配列メンバ(Flexible Array Member)
[編集]C99以降では、構造体の最後のメンバとして、サイズが指定されていない配列(柔軟な配列メンバ)を宣言することができます:
struct Buffer { int size; char data[]; // 柔軟な配列メンバ }; // 使用例 struct Buffer *createBuffer(int size) { struct Buffer *buf = malloc(sizeof(struct Buffer) + size); if (buf) { buf->size = size; } return buf; }
C23では、柔軟な配列メンバの扱いが改善され、より多くのコンテキストで使用できるようになりました。
匿名構造体と共用体(C11以降)
[編集]C11からは、構造体内に名前のない(匿名の)構造体や共用体を含めることができます:
struct Point3D { union { struct { float x, y, z; }; // 匿名構造体 float coords[3]; }; // 匿名共用体 }; struct Point3D p; p.x = 1.0f; // 直接アクセス可能 p.coords[1] = 2.0f; // yと同じメモリ領域
まとめ
[編集]構造体は、関連するデータを一つの単位として扱うための強力な機能です。C23では、構造体の取り扱いが更に強化され、より柔軟なプログラミングが可能になりました。構造体を適切に使用することで、コードの可読性と保守性を高めることができます。
構造体はC言語のオブジェクト指向プログラミングの基礎となる要素であり、カプセル化の原則を実現するための重要なメカニズムです。より複雑なシステムを設計する際には、構造体を効果的に活用することで、データと操作を論理的にグループ化することができます。