
C言語基礎|構造体をメンバに持つ構造体
「データは“入れ子”で強くなる。構造体をメンバに持たせて、現実の形にもっと近づけよう!」
構造体に慣れてくると、次にやりたくなるのが「もっと現実っぽい形でデータを表したい」です。
現実のモノって、だいたい“部品”を持っていますよね。
たとえば「配送先」には住所があって、住所には郵便番号や都道府県があって…みたいに、
まとまりの中に、さらにまとまりがあることが多いです。
C言語では、こういう“入れ子”の表現ができます。
つまり、構造体のメンバとして構造体を持てるんです。
これができると、
- コードの意味が自然になる。
- メンバの関連が崩れにくい。
- まとまり単位で処理できて読みやすい
と、いいことがたくさん出てきます。

まず結論:構造体のメンバは構造体でもOK
書式(構造体をメンバにする)
struct Outer {
struct Inner inner;
...
};
または typedef を使って、もっと読みやすくできます。
typedef struct { ... } Inner;
typedef struct {
Inner inner;
...
} Outer;
「住所(Address)」を構造体で表し、それを「配送ラベル(Label)」のメンバとして持たせます。
構造体をメンバに持つイメージ(図で理解)
図:Label の中に Address が入っている(入れ子)

図の説明
- addr は「住所1つ分のまとまり」
- zip/city/street が住所の部品
- Label の中に Address が“丸ごと”入るので、現実の構造に近いです
サンプルプログラム(構造体メンバとして構造体を使う)
仕様
- Address(郵便番号・市区町村・番地)を構造体で表す
- Label(宛先名・住所)を構造体で表す(住所は Address をメンバにする)
- 入力して、整形表示する
プロジェクト名:chap12-8-1 ソースファイル名:chap12-8-1.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <stdio.h>
#define NAME_LEN 32
#define ZIP_LEN 8
#define CITY_LEN 32
#define STREET_LEN 64
typedef struct {
char zip[ZIP_LEN]; // 郵便番号(例:123-4567)
char city[CITY_LEN]; // 市区町村
char street[STREET_LEN]; // 番地など
} Address;
typedef struct {
char to[NAME_LEN]; // 宛先名
Address addr; // 住所(構造体をメンバにする)
} Label;
void print_label(Label x)
{
printf("\nラベルを作成しました。\n");
printf("宛先:%s\n", x.to);
printf("〒%s\n", x.addr.zip);
printf("%s %s\n", x.addr.city, x.addr.street);
}
int main(void)
{
Label l;
printf("宛先名を入力:");
scanf("%s", l.to);
printf("郵便番号を入力:");
scanf("%s", l.addr.zip);
printf("市区町村を入力:");
scanf("%s", l.addr.city);
printf("番地を入力:");
scanf("%s", l.addr.street);
print_label(l);
return 0;
}このプログラムで登場する各項目を表で詳しく解説
typedef struct { ... } Address;
書式
typedef struct {
...
} Address;
何をする?
- 構造体型を定義し、それに Address という型名(typedef名)を付けます。
- 以後、Address だけで型として使えます。
表:Address のメンバ
| メンバ | 型 | 意味 |
|---|---|---|
| zip | char配列 | 郵便番号 |
| city | char配列 | 市区町村 |
| street | char配列 | 番地など |
説明
住所という“まとまり”は、zip/city/street のセットなので、Address としてまとめるのが自然です。
typedef struct { ... } Label;
書式
typedef struct {
char to[...];
Address addr;
} Label;
何をする?
- Label という型を作ります。
- そして addr というメンバに Address 型を入れています(ここが主役!)。
Label のメンバ
| メンバ | 型 | 意味 |
|---|---|---|
| to | char配列 | 宛先名 |
| addr | Address | 住所(構造体のまとまり) |
説明
Label は「宛先名」+「住所」という構造になっていて、住所は Address としてひとまとめに持ちます。
メンバアクセス(ドット演算子)が2段になる
構造体の中に構造体があると、アクセスがこうなります。
アクセス例
| 書き方 | 意味 |
|---|---|
| l.to | ラベルの宛先名 |
| l.addr | ラベルの住所(Address ひとまとまり) |
| l.addr.zip | 住所の郵便番号 |
| l.addr.city | 住所の市区町村 |
| l.addr.street | 住所の番地 |
図:l.addr.zip の読み方
l(Label)
└ addr(Address)
└ zip(郵便番号)
図の説明
「Label の addr の zip」という順番で辿ります。
これが“入れ子構造”をそのままコードに落とした形です。
関数に構造体を渡す(print_label の役割)
書式(関数定義)
戻り値型 関数名(引数...)
{
...
}
今回の print_label はこうでした。
void print_label(Label x)
何をする?
- Label x を受け取り、ラベルを整形して表示します。
- 表示だけが目的なので戻り値は void です。
print_label の中で使う要素
| 要素 | 何をする? |
|---|---|
| printf | 画面へ文字列を表示する |
| x.addr.zip など | 入れ子メンバにアクセスして表示する |
入力での注意:char配列は & が不要
scanf は基本的に「書き込み先の住所」が必要です。
ただし char配列は配列名が先頭要素を指すように扱われるので、そのまま渡します。
scanf と & の要不要
| 入力先 | 例 | & |
|---|---|---|
| char配列 | l.to | 不要 |
| char配列 | l.addr.zip | 不要 |
| int など | &value | 必要 |
こういうときに「構造体メンバの構造体」が効く
効果が出る場面
| 場面 | なぜ便利? |
|---|---|
| 住所・座標・日付のような“部品まとまり”を複数の場所で使う | Address や Point を再利用できる |
| 関数の引数をスッキリさせたい | Address を1つ渡すだけで済む |
| 関連のある項目がズレるのを防ぎたい | zip/city/street を常に一体として扱える |
まとめ:入れ子構造は“現実に近い設計”
- 構造体のメンバは、基本型だけでなく配列や構造体でもOK
- 構造体をメンバに持つと、現実の「部品のまとまり」を自然に表現できる。
- アクセスはドット演算子が2段になる(l.addr.zip のように辿る)
- まとまりが強くなって、コードが読みやすく、ミスもしにくくなる。
