構造体の埋め込み
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 28/61。
C++やJavaのようなオブジェクト指向言語では、継承によってクラスが親クラスからプロパティを引き継ぐことができます。C言語には組み込みの継承はありませんが、コンポジション(合成)と呼ばれる、あるstructを別のstructの中に配置する手法を使って、継承をシミュレートすることができます。
考え方は単純です。もし Child が Parent のすべてのプロパティを必要とするなら、Parent 構造体を Child のメンバーとして埋め込みます。
typedef struct {
int id;
char name[50];
} Parent;
typedef struct {
Parent parent; // 埋め込み構造体
int grade;
} Child;ここで、ChildはParentが持つすべてを含み、さらに独自のgradeフィールドを持っています。これはコンポジションです。つまり、子は親である(is a)のではなく、親を持っている(has a)ということですが、その効果は継承に似ています。
子のインスタンスを通じて親のメンバにアクセスするには、ドット演算子を2回使用します:
Child c;
c.parent.id = 101;
c.parent.name[0] = 'A';
c.grade = 5;このパターンは、C言語における型階層を構築するための基礎となります。基本の struct を最初のメンバとして埋め込むことで、今後のレッスンで詳しく見ていく強力なテクニックが利用可能になります。
チャレンジ
簡単C言語における継承の基礎となる、構造体の埋め込み(入れ子)を実証するシンプルな従業員管理システムを構築しましょう。
Employee が Person を含む階層を作成します。これは継承の仕組みを反映しています。つまり、従業員は追加の仕事関連情報を持つ「人(person)」であるということです。Employee 構造体の中に Person 構造体を埋め込むことで、人のすべてのプロパティに従業員固有のフィールドを加えたものを得ることができます。
コードを3つのファイルに分けて構成します:
types.h: インクルードガードを使用して、ここで両方の構造体を定義します。まず、name(50文字の文字配列)とage(整数)の2つのフィールドを持つPerson構造体を作成します。次に、Personを最初のメンバとして埋め込み、employee_idフィールド(整数)を追加したEmployee構造体を定義します。types.c:Employeeへのポインタを受け取り、その情報を表示するprint_employeeという関数を実装します。学習したドット/アロー演算子の連鎖構文を使用して、埋め込まれた人のデータにアクセスします。main.c:Employee変数を作成し、すべてのフィールド(埋め込まれた人の名前と年齢を含む)に値を入力し、作成した表示関数を使用して情報を表示します。
3つの入力を受け取ります:従業員の名前(文字列)、年齢(整数)、および従業員ID(整数)です。
print_employee 関数は、従業員の情報を以下の形式で出力する必要があります:
Employee: Alice
Age: 28
ID: 1001ここで、Alice は名前、28 は年齢、1001 は入力された従業員IDです。
埋め込まれた構造体のメンバには、親メンバを介してアクセスすることを忘れないでください。例えば、名前にアクセスするには、まず person メンバを経由して指定します。
チートシート
C言語には組み込みの継承機能はありませんが、コンポジション(合成) — ある構造体を別の構造体の中に埋め込むこと — を使用して、それをシミュレートすることができます。
親子関係を作成するには、親構造体を子構造体のメンバーとして埋め込みます。
typedef struct {
int id;
char name[50];
} Parent;
typedef struct {
Parent parent; // 埋め込まれた構造体
int grade;
} Child;子構造体は、親のすべてのプロパティに加えて、独自に追加されたフィールドを保持します。これは「has-a(〜を持っている)」関係を表します。
埋め込まれた構造体のメンバーにアクセスするには、ドット演算子を2回使用します。
Child c;
c.parent.id = 101;
c.parent.name[0] = 'A';
c.grade = 5;ポインタを扱う場合は、アロー演算子を使用して埋め込まれた構造体のメンバーにアクセスします。
Child *ptr = &c;
ptr->parent.id = 101;
ptr->grade = 5;ベースとなる構造体を最初のメンバーとして埋め込むことは、C言語で型階層を構築するための高度なテクニックを可能にする一般的なパターンです。
自分で試してみよう
#include <stdio.h>
#include <string.h>
#include "types.h"
int main() {
// 入力を読み込む
char name[50];
int age;
int employee_id;
fgets(name, 50, stdin);
name[strcspn(name, "\n")] = '\0'; // 改行を削除する
scanf("%d", &age);
scanf("%d", &employee_id);
// TODO: Employee 変数を作成する
// TODO: 埋め込まれた person の name と age に値を代入する
// ヒント: name には strcpy を使用し、employee.person.name でアクセスします
// TODO: employee_id を設定する
// TODO: 作成した employee へのポインタを引数にして print_employee を呼び出す
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。