
C言語基礎|構造体を使ったデータ設計
「Point を作ったら、次は Car。構造体の組み合わせで“現実の設計”がそのままコードになる!」
構造体を覚えると、ただ値をまとめるだけじゃなくて、現実のモノをどう表すか(データ設計)ができるようになります。
ここがめちゃくちゃ楽しいところです。
現実の「自動車」を考えると、必要な情報はいくつかあります。
たとえば「現在位置」と「燃料」。
さらに「現在位置」は、X座標とY座標のセットですよね。
つまり、
- 座標は Point(x と y)という“まとまり”
- 自動車は Car(pt と fuel)という“まとまり”
のように、構造体を部品として組み立てるのが自然です。
この記事では、この “部品化して組み立てる” という視点で、構造体を使ったデータ設計をやさしく整理します。
そして最後に、同じ考え方を使ったシンプルなプログラム例も動かして確認します。

現実を分解して、構造体で組み立てる
まずは「何を部品として切り出すか」がデータ設計の第一歩です。
現実世界 → プログラムの部品
| 現実の概念 | プログラムでの部品 | 中身 |
|---|---|---|
| 座標 | Point | x, y |
| 自動車 | Car | pt(Point), fuel |
説明
- 座標はいつでも x と y がセットなので、Point として独立させると再利用しやすいです。
- 自動車は「位置」と「燃料」のセットなので、Car としてまとめると意味がハッキリします。
メンバ と 構成メンバ(分解の考え方)
ここが設計として面白いポイントです。
Car のメンバは2個なのに、分解すると“部品の数”は増えます。
図:Car のメンバと構成メンバ
Car
メンバ:pt と fuel
pt は Point なので中身がある
pt.x
pt.y
構成メンバ(これ以上分解できない単位)
pt.x, pt.y, fuel
Car の数え方
| 種類 | 内容 | 個数 |
|---|---|---|
| メンバ | pt, fuel | 2 |
| 構成メンバ | pt.x, pt.y, fuel | 3 |
説明
- pt は“まとまり”としてのメンバ
- pt.x と pt.y は最小単位(構成メンバ)
- どこまで分解して考えるかで、設計の見通しが良くなります
ドット演算子が2段になる(入れ子アクセス)
Car の位置は pt に入っていて、pt の中に x と y があるので、アクセスがこうなります。
アクセス例(超定番)
| 書き方 | 意味 |
|---|---|
| c.pt | 自動車 c の現在位置(Point) |
| c.fuel | 自動車 c の残り燃料 |
| c.pt.x | 現在位置のX座標 |
| c.pt.y | 現在位置のY座標 |
図:c.pt.x の読み方
c(Car)
└ pt(Point)
└ x(X座標)
説明
ドット演算子は左から順に結びつく(左結合)ので、c.pt.x は (c.pt).x と同じ意味です。
そのため、普段は括弧なしで書けます。
ポインタで受けると -> が混ざる(更新する関数の定番)
自動車を移動させるような関数は、内部で Car の中身を更新したいので、Car へのポインタを受け取る形が定番です。
書式(ポインタ先のメンバアクセス)
p->mem
意味
- p が指す構造体のメンバ mem を表します
. と -> の使い分け
| 対象 | 記法 | 例 |
|---|---|---|
| 構造体変数 | . | c.fuel |
| 構造体へのポインタ | -> | cptr->fuel |
サンプルプログラム
仕様
- Point と Car を定義(Car は Point をメンバに持つ)
- 移動先 dest までの距離を使って燃料が減る。
- 燃料不足なら移動しない。
- 結果を表示する。
プロジェクト名:chap12-9-1 ソースファイル名:chap12-9-1.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <math.h>
#include <stdio.h>
#define sqr(n) ((n) * (n))
typedef struct {
double x;
double y;
} Point;
typedef struct {
Point pt; // 現在位置
double fuel; // 残り燃料(距離と同じだけ減る)
} Car;
double distance_of(Point a, Point b)
{
return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}
int move_to(Car *c, Point dest)
{
double d = distance_of(c->pt, dest);
if (d > c->fuel) {
return 0; // 移動失敗
}
c->pt = dest;
c->fuel -= d;
return 1; // 移動成功
}
int main(void)
{
Car mycar = {{0.0, 0.0}, 10.0};
Point dest = {6.0, 8.0};
printf("移動前:位置(%.1f, %.1f) 燃料%.1f\n",
mycar.pt.x, mycar.pt.y, mycar.fuel);
if (move_to(&mycar, dest)) {
printf("移動しました。\n");
} else {
printf("燃料が足りず移動できません。\n");
}
printf("移動後:位置(%.1f, %.1f) 燃料%.1f\n",
mycar.pt.x, mycar.pt.y, mycar.fuel);
return 0;
}プログラムに登場する項目を表で詳しく解説
Point と Car の宣言(データ設計の核)
書式(typedef を使った構造体型定義)
typedef struct {
...
} 型名;
何をする命令?
- 構造体型を定義して、型名を1単語で使えるようにします。
今回の型設計
| 型名 | 役割 | メンバ |
|---|---|---|
| Point | 座標を表す部品 | x, y |
| Car | 自動車を表す本体 | pt(Point), fuel |
説明
Point を部品化しておくと、Car 以外(例:目的地、チェックポイント、地図の点)にも使い回せます。
distance_of(距離を計算する関数)
書式
戻り値型 関数名(引数...)
今回:
double distance_of(Point a, Point b)
何をする命令?
- 2点 a と b の距離を求めて返します。
図:2点間距離のイメージ

説明
座標の差(x差、y差)から、三平方の形で距離を出しています。
move_to(Car を更新する関数)
書式
int move_to(Car *c, Point dest)
何をする命令?
- c が指す自動車を dest へ移動させます(移動できるなら)
- 移動距離分だけ燃料を減らします
- 成功なら 1、失敗なら 0 を返します
move_to の処理手順
| 手順 | 処理 | 使う要素 |
|---|---|---|
| 1 | 距離 d を求める | distance_of(c->pt, dest) |
| 2 | 燃料が足りるか確認 | d > c->fuel |
| 3 | 位置を更新 | c->pt = dest |
| 4 | 燃料を更新 | c->fuel -= d |
図:更新されるメンバ
移動先を代入:c->pt = dest
燃料を減らす:c->fuel -= d
説明
Car の中身を変えるので、引数は Car *c です。
ここで -> を使うことで、ポインタ先のメンバをスッキリ書けます。
データ設計としての学び(ここが本題)
この例のポイントは、プログラムのテクニックというより 設計の分け方です。
設計のコツ
| コツ | 内容 | この例での対応 |
|---|---|---|
| まとまりは部品にする | 再利用できる単位で切る | Point を独立させる |
| 本体は部品を持つ | 現実の形に合わせる | Car が Point を持つ |
| 更新が必要ならポインタ | 状態を変える関数の定番 | move_to(Car *c, ...) |
| 参照だけなら値渡しでもOK | 読むだけなら安全 | distance_of(Point a, Point b) |
演習問題
演習12-5
上のプログラムを改造して、移動の指定方法を次の2通りから選べるようにしてください。
- 1:目的地の座標を入力する(dest.x と dest.y を入力)
- 2:現在地からの移動量を入力する(dx と dy を入力し、dest = 現在地 + 移動量 で目的地を作る)
例:現在地が (5.0, 3.0) で (7.5, 8.9) に行きたい
- 座標入力なら 7.5 と 8.9
- 移動量入力なら 2.5 と 5.9
解答例
プロジェクト名:chap12-9-2 ソースファイル名:chap12-9-2.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <math.h>
#include <stdio.h>
#define sqr(n) ((n) * (n))
typedef struct { double x, y; } Point;
typedef struct { Point pt; double fuel; } Car;
double distance_of(Point a, Point b)
{
return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}
int move_to(Car *c, Point dest)
{
double d = distance_of(c->pt, dest);
if (d > c->fuel) return 0;
c->pt = dest;
c->fuel -= d;
return 1;
}
int main(void)
{
Car mycar = {{5.0, 3.0}, 20.0};
Point dest;
int mode;
printf("現在地(%.1f, %.1f) 燃料%.1f\n", mycar.pt.x, mycar.pt.y, mycar.fuel);
printf("移動方法を選択(1:座標 2:移動量):");
scanf("%d", &mode);
if (mode == 1) {
printf("目的地X:"); scanf("%lf", &dest.x);
printf("目的地Y:"); scanf("%lf", &dest.y);
} else {
double dx, dy;
printf("移動量dx:"); scanf("%lf", &dx);
printf("移動量dy:"); scanf("%lf", &dy);
dest.x = mycar.pt.x + dx;
dest.y = mycar.pt.y + dy;
}
if (move_to(&mycar, dest)) {
printf("移動しました。\n");
} else {
printf("燃料が足りず移動できません。\n");
}
printf("現在地(%.1f, %.1f) 燃料%.1f\n", mycar.pt.x, mycar.pt.y, mycar.fuel);
return 0;
}解説(ポイント)
- mode で入力方法を切り替え。
- 移動量入力では dest を計算で作る(現在地+移動量)
- move_to は同じまま使える(設計が分離できている証拠)
