WisdomSoft - for your serial experiences.

7.4 構造体型のメンバ

構造体のメンバに、他の構造体型を指定できます。複数の構造を組み合わせた複雑な構造や、構造体へのポインタをメンバに持つ構造体などを作れます。

7.4.1 構造体型のメンバを持つ構造体

構造体は、メンバに他の構造体型を含ませることができます。これは、構造体の性質によっては便利な場合があります。例えば、座標を表す Point 構造体と、サイズを表す Size 構造体があるとして、これらの構造体のメンバを含む Rectangle 構造体を作ることができます。

struct Point { int x , y; };
struct Size { int width , height; };
struct Rectangle {
	struct Point location;
	struct Size size;
};

Rectangle 構造体は、Point と Size 構造体型のメンバを含んでいます。長方形を現す Rectangle の情報は座標とサイズで構成されているため、このような形が有効であると考えられます。もちろん、実践向けを考えればメンバを辿るのが面倒なので単純に4つの int 型メンバを宣言するべきであるという主張もあることでしょう。Rectangle の幅 width を参照するには次のように記述します。

rect.size.width
rect->size.width

上は通常の変数から、下はポインタからアクセスした場合です。このように、最も外側の構造体から順にメンバを辿っていきます。

コード1
#include <stdio.h>

struct Point { int x , y; };
struct Size { int width , height; };
struct Rectangle {
	struct Point location;
	struct Size size;
};

int main() {
	struct Rectangle rect = { 100 , 50 , 400 , 300 };

	printf("Location (%d , %d) : Size (%d , %d)\n" ,
		rect.location.x , rect.location.y ,
		rect.size.width , rect.size.height
	);
	return 0;
}
実行結果
コード1 実行結果

このプログラムは、Point や Size 構造体を含む Rectangle 構造体を宣言して、これを利用しています。初期化子を見れば認識できると思いますが、たとえ構造体型のメンバを持っても基本的な考え方は同じで、Rectangle 構造体の実体は int 型4つ分であるという事実は変わらないのです。

この方法を用いて「7.3 構造体の型変換 コード2」を改良し、より確実に基本となる構造体を継承できます。

コード2
#include <stdio.h>

struct Color {
	char *name;
	int r , g , b;
};
struct ColorEx {
	struct Color color;
	int a;
};

void SetColor(struct Color *color) {
	printf(
		"%s r = %d : g = %d : b = %d\n" ,
		color->name , color->r , color->g , color->b
	);
}

int main() {
	struct Color color = { "Color" , 0xFF , 0 , 0 };
	struct ColorEx colorEx = { "ColorEx" , 0 , 0xFF , 0 , 0xA0 };

	SetColor(&color);
	SetColor((struct Color *)&colorEx);

	return 0;
}
実行結果
コード2 実行結果

ColorEx 構造体の最初のメンバが Color 構造体型であることに注目してください。以前は Color 構造体と同じになるように name、r、g、b をそれぞれ再定義していましたが、コード2の ColorEx はより確実に基本となる Color 構造体を継承しています。デザインとしては、このような書き方のほうが整合性が高くなるため、安全性が向上すると考えることができます。

7.4.2 自己参照的構造体

構造体が、構造体型のメンバを保有できることがわかりました。では、その構造体自身をメンバとして保有した場合はどうなるのでしょう。このような構造体を、自己参照的構造体と呼びます。例えば、次のような構造体の宣言です。

struct Node { struct Node node; };

残念ながら、これはエラーとなります。しかし、諦めるのはまだ早いです。自己参照の目的は構造体のメンバとして自分と同じ情報を持つ存在にアクセスすることです。構造体はポインタ型のメンバを保有することができるため、次の宣言であれば有効です。

struct Node { struct Node *node; };

この Node 構造体は Node 型へのポインタを保有します。このポインタから間接参照を行うことによって、自己参照的な構造体を作ることができるようになります。自己参照的な構造体は、親子関係の存在するオブジェクトなどを建築する時に適しています。例えば、ウィンドウシステムのコンポーネント関係や、メニューやツリーで用いられる項目の階層関係です。木構造の性質を持った情報を扱う場合、同質の親と子を保有する可能性が高く、こうした情報の管理には自己参照的な構造体が役に立つでしょう。 

コード3
#include <stdio.h>

struct Chain {
	char *text;
	struct Chain *next;
};

void ShowChain(struct Chain *chain) {
	if (chain == NULL) return;

	printf("%s\n" , chain->text);
	ShowChain(chain->next);
}

int main() {
	struct Chain first = { "First Chain" };
	struct Chain second = { "Second Chain" };
	struct Chain third = { "Third Chain" };

	first.next = &second;
	second.next = &third;

	ShowChain(&first);

	return 0;
}
実行結果
コード3 実行結果

コード3は、テキストデータの関連を結びつける自己参照的な構造体 Chain を宣言しています。この構造体を用いれば、自己参照によって、まさに鎖のようなデータ関係を建築することができます。ShowChain() メソッドは、与えられた Chain へのポインタから鎖をたどり、要素がなくなる(next メンバが NULL)まで再帰処理を繰り返します。