第1メンバーの規則
CoddyのCジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 29/61。
ある構造体を別の構造体の first member として埋め込むと、強力なことが起こります。外側の構造体のメモリアドレスは、その first member のメモリアドレスと同一になります。これは C 標準によって保証されています。
前の例を考えてみましょう:
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // 最初のメンバー
int grade;
} Child;Child 変数を作成すると、Parent member は Child の memory のまさに先頭に位置します。つまり、Child へのポインターは、その Parent member へのポインターとまったく同じ address を指します。
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // 安全なキャスト!2つのポインタは同じ address を保持します。これにより、Child* を Parent* を想定する任意の function に渡せます:
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
// main内:
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // 完璧に動作するこの技法は、Cにおける継承の基盤です。基底型用に記述されたfunctionは、基底型が最初にembeddedされている限り、あらゆる派生型で使用できます。castが安全なのは、memory layoutsがalign perfectlyするbecauseです。
チャレンジ
簡単単純な動物階層を構築しながら、first member ルールを詳しく見ていきましょう。ベースとなる Animal 構造体と派生した Dog 構造体を作成し、Dog へのポインターを Animal へのポインターへ安全にキャストできる方法を示します。
適用する重要なポイントは次のとおりです。Animal が Dog の first member である場合、両方の構造体は同じ開始メモリアドレスを共有します。つまり、ポインターをキャストすることで、Dog* を Animal* を要求する任意の function に渡せます。
コードを3つのファイルに分けて整理します。
animal.h:include guard を使用して両方の構造体を Define します。Animal構造体には、単一の fieldlegs(脚の数を表す整数)を持たせます。Dog構造体には、Animalを first member として embedded し、さらにnamefield(50文字の文字配列)を追加します。また、Animal*parameter を受け取る functionprint_animalも Declare します。animal.c:print_animalfunction を Implement します。この function はベース型を扱います。Animalについてのみ認識し、その animal の脚の数を Print します。main.c:Dogvariable を Create し、その field(embedded された animal の脚と dog の名前の両方)に値を設定します。次に、Dog*をAnimal*にキャストしてprint_animalに渡すことで、first member ルールを示します。完全な動作を示すため、dog の名前も別に Print します。
2つの入力を受け取ります。dog の 名前(string)と 脚の数(integer)です。
出力は次のようになります。
Dog: Buddy
Legs: 4ここで、Buddy は dog の名前、4 は脚の数です。まず main.c から dog の名前を Print し、その後、キャストしたポインターを使って print_animal を call し、脚の数を表示します。
これは、ベース型(Animal)用に書かれた function が、ポインターのキャストによって派生型(Dog)でもシームレスに動作できることを示しています。これは C における継承の基礎です。
自分で試してみよう
#include <stdio.h>
#include <string.h>
#include "animal.h"
int main() {
// 入力を読み取る
char name[50];
int legs;
scanf("%s", name);
scanf("%d", &legs);
// TODO: Dog変数を作成する
// TODO: strcpyを使って犬の名前を設定する
// TODO: 脚の数を設定する(埋め込まれたAnimal経由でアクセス)
// TODO: 犬の名前を "Dog: <name>" の形式で出力する
// TODO: Dog*をAnimal*にキャストしてprint_animalを呼び出す
// これは最初のメンバールールを示しています!
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Cオンラインコンパイラ