C言語のきほん|構造体宣言とtypedefの活用

構造体の書き方を整理すると、コードはもっと短く、もっと読みやすくなる。

構造体を使えるようになると、関連するデータをひとまとまりにして扱えるようになり、C言語のプログラムがぐっと整理しやすくなります。
ただし、構造体は書き方が1つだけではありません。実は、目的に応じていくつかの宣言方法を使い分けることができます。

たとえば、構造体の型だけを先に宣言してあとから変数を作る方法もあれば、型の宣言と変数の定義を同時に行う方法もあります。さらに、タグ名を省略した無名構造体や、typedef を使って別名を付ける方法もあります。

このあたりは最初は少しややこしく感じるかもしれませんが、ひとつひとつ整理して見ていくと、それぞれの役割はとても分かりやすいです。
特に typedef は、構造体を使うたびに毎回 struct と書かなくてよくなるので、コードをすっきり見せたいときにとても便利です。

構造体の宣言方法を理解すると、単に文法を覚えるだけでなく、

  • どの書き方が読みやすいか
  • どの場面で使うと自然か
  • どの方法が再利用しやすいか

といった視点も身についてきます。

ここでは、構造体のいろいろな宣言方法を順番に整理しながら、typedef の便利さや使いどころまで、やさしく丁寧に確認していきましょう。

構造体の宣言方法は1つではない

これまで学んできた基本の書き方は、まず構造体の型を宣言し、そのあとで変数を宣言する形でした。

struct point {
    int x;
    int y;
};

struct point p1;

この形はとても分かりやすく、構造体の基本として最初に覚えるのにぴったりです。
ただ、C言語ではこれ以外にもいくつかの書き方が用意されています。

今回押さえたいのは、次の4つです。

宣言方法特徴
型の宣言と実体の定義を同時に行う1か所でまとめて書ける
定義と同時に初期化を行う値を最初から入れられる
タグ名を省略して定義する一度きりの用途に使える
typedef を使うstruct を省略して書ける

どれも文法としては正しいですが、使いやすさや再利用のしやすさに違いがあります。
その違いを理解すると、コードの見通しがかなりよくなります。

まずは基本形を確認する

比較しやすいように、まずは通常の構造体宣言を見ておきましょう。

struct point {
    int x;
    int y;
};

struct point p1;

この書き方では、

  • struct point で構造体の型を宣言する
  • struct point p1; でその型の変数を作る

という2段階になっています。

この方法のよいところは、型の定義と変数の定義が分かれているため、役割がはっきり見えることです。
一方で、変数を宣言するたびに struct point と書かなければならないので、少し長く感じることがあります。

型の宣言と実体の定義を同時に行う

最初のバリエーションは、構造体の型を宣言すると同時に、その変数も定義する方法です。

struct point {
    int x;
    int y;
} p1;

この書き方では、構造体の型 point を宣言しながら、同時に p1 という変数も作っています。

この書き方のポイント

  • 型の宣言と変数の定義を1か所にまとめられる
  • すぐに使う変数が決まっている場合には便利
  • あとで別の変数を作るときは、やはり struct point と書く必要がある

つまり、「型だけを先に作る」のではなく、「型と最初の1個の変数を一緒に用意する」イメージです。

表で整理する

書き方意味
struct point { ... };型だけを宣言
struct point { ... } p1;型を宣言しながら p1 も定義

この形は文法として自然ですが、設計を整理して書きたい場合は、型宣言と変数宣言を分けた方が読みやすいことも多いです。

定義と同時に初期化を行う

さらに、型の宣言と変数の定義を同時に行うだけでなく、その場で初期化もできます。

struct point {
    int x;
    int y;
} p1 = {10, 20};

この場合は、

  • p1.x に 10
  • p1.y に 20

が入ります。

この書き方の特徴

  • 宣言、定義、初期化を一度に書ける
  • コードをコンパクトにできる
  • 初期値がすぐ分かるので見やすい場合もある

ただし、型定義と変数定義と初期化が1行のまとまりになるため、長くなると少し読みにくく感じることもあります。

タグ名を省略して定義する

次に、構造体タグ名を省略する方法です。

struct {
    int x;
    int y;
} p1 = {10, 20};

これは無名構造体と呼ばれる形です。
構造体の中身は定義されていますが、point のようなタグ名がありません。

無名構造体の特徴

  • p1 は作れる
  • でも、その構造体型に名前がない
  • 同じ型で別の変数を作りたくても、型名で再利用できない

つまり、この書き方は「この場でこの変数だけ使えればよい」という用途には使えますが、一般的な再利用にはあまり向いていません。

なぜ再利用しにくいのか

たとえば次のようには書けません。

struct ??? p2;

型に名前がないので、あとからその型を指定できないからです。

表で整理する

書き方型名があるか再利用しやすいか
struct point { ... } p1;あるしやすい
struct { ... } p1;ないしにくい

無名構造体は文法上使えますが、初心者のうちは「型名がないので再利用しにくい」という点をしっかり押さえておくとよいです。

typedef を使って宣言を簡潔にする

ここでとても便利なのが typedef です。
typedef を使うと、構造体型に別名を付けることができます。

たとえば次のように書きます。

typedef struct {
    int x;
    int y;
} Point;

これで Point という新しい型名が使えるようになります。

その結果、変数宣言は次のように書けます。

Point p1 = {100, 200};

これまでのように struct point p1; と書く必要がなくなり、かなりすっきりします。

typedef のよさ

利点内容
struct を毎回書かなくてよい変数宣言が短くなる
コードが見やすい型名が自然に読める
再利用しやすい他の場所でも同じ型名を使いやすい
大きなプログラムで便利宣言が増えても読みやすさを保ちやすい

このため、実際のコードでは typedef を使った構造体宣言がよく使われます。
学習が進んでいくと、この形を基本として扱うことが多くなります。

typedef の書き方を整理する

typedef の書き方はいくつかありますが、まずは次の形を基本として覚えると分かりやすいです。

typedef struct {
    int x;
    int y;
} Point;

この書き方の意味を分解すると、

  • struct { ... } で構造体を定義する
  • typedef によってその型に Point という別名を付ける

という流れです。

すると、以後は Point が型名として使えます。

Point a;
Point b;
Point points[3];

この見た目は、int や double と同じように自然ですね。
これが typedef の大きな魅力です。

この図では、構造体の宣言方法ごとの違いを一覧で比較できます。
特に大切なのは、無名構造体には型名がないため再利用しにくいこと、そして typedef を使うと型名が短く扱いやすくなることです。
コードを読みやすく保ちたい場面では、typedef の利点がかなり大きいと分かります。

サンプルプログラムで typedef の便利さを確認する

ここでは、元の point の例を、より分かりやすい商品情報の例に置き換えて説明します。
表示メッセージも日本語にし、コメントも日本語にしています。

ファイル名:15_4_1.c

#include <stdio.h>

// 商品情報を表す構造体に Item という別名を付ける
typedef struct {
    int code;       // 商品番号
    double price;   // 価格
} Item;

int main(void)
{
    // typedef で定義した型名を使って変数を宣言し、初期化する
    Item item1 = {101, 1980.0};
    Item item2 = {102, 2500.0};

    printf("商品1: 番号=%d 価格=%.0f円\n", item1.code, item1.price);
    printf("商品2: 番号=%d 価格=%.0f円\n", item2.code, item2.price);

    return 0;
}

実行結果例

商品1: 番号=101 価格=1980円
商品2: 番号=102 価格=2500円

サンプルプログラムを読み解く

最初に、typedef を使って構造体型に Item という別名を付けています。

typedef struct {
    int code;
    double price;
} Item;

この時点で、Item は型名として使えるようになります。

そのため、変数宣言では次のように書けます。

Item item1 = {101, 1980.0};
Item item2 = {102, 2500.0};

もし typedef を使わないなら、次のように書く必要があります。

struct item {
    int code;
    double price;
};

struct item item1 = {101, 1980.0};
struct item item2 = {102, 2500.0};

もちろんこれでも正しいのですが、何度も struct item と書くのは少し長く感じます。
typedef を使うと、その部分を Item に置き換えられるので、かなり読みやすくなります。

typedef は新しい構造体を作るものではない

ここでひとつ大事な点があります。
typedef は、新しい構造体の仕組みを生み出しているわけではありません。
あくまで既存の型に別名を付けているだけです。

つまり、

typedef struct {
    int x;
    int y;
} Point;

は、「Point という新しい概念の構造体を発明した」というより、
「この構造体型を Point という名前で呼べるようにした」と考えると分かりやすいです。

タグ名付き typedef という書き方もある

typedef には、タグ名を付けたまま別名を作る書き方もあります。

typedef struct point {
    int x;
    int y;
} Point;

この場合は、

  • struct point というタグ名もある
  • Point という別名もある

という状態になります。

そのため、どちらでも使えます。

struct point a;
Point b;

ただ、学習の最初の段階では、タグ名なしの typedef の方がシンプルで分かりやすいことが多いです。

どの宣言方法を使えばよいのか

実際にどの書き方を使うかは、目的次第です。
ただ、一般的な考え方としては次のように整理できます。

書き方向いている場面
基本の struct タグ名構造体の仕組みを学ぶとき
型と変数を同時定義その場で1個だけすぐ使いたいとき
無名構造体一度きりで再利用しないとき
typedef以後も何度も使う型を読みやすくしたいとき

学習用としては基本形を理解することが大切ですが、実際のコードを読みやすく保ちたいなら typedef はとても有力です。

実践問題

次の要件を満たすプログラムを作成してください。

① 本の情報を表す構造体 book_info を定義する。構造体には以下のメンバを持たせる。
 code:本の番号(int型)
 title:本のタイトル(char型配列、最大20文字)
 price:価格(double型)
 genre:ジャンル名を表す文字列(char型配列、最大20文字)
② 構造体の配列を使って、3冊分の本の情報を管理する。
③ genre 以外の情報は初期化で設定する。
④ genre は初期化後に設定する。
⑤ 各本の情報を表示する。

解答例

ファイル名:15_4_2.c

#include <stdio.h>
#include <string.h>

// 本の情報を表す構造体
struct book_info {
    int code;          // 本の番号
    char title[20];    // タイトル
    double price;      // 価格
    char genre[20];    // ジャンル
};

int main(void)
{
    struct book_info books[3] = {
        {1, "C言語基礎", 2200.0, ""},
        {2, "配列入門", 1800.0, ""},
        {3, "関数のしくみ", 2500.0, ""}
    };

    strcpy(books[0].genre, "技術書");
    strcpy(books[1].genre, "入門書");
    strcpy(books[2].genre, "参考書");

    printf("本の情報\n");
    for (int i = 0; i < 3; i++) {
        printf("本%d:番号 %d, タイトル %s, 価格 %.0f円, ジャンル %s\n",
            i + 1,
            books[i].code,
            books[i].title,
            books[i].price,
            books[i].genre);
    }

    return 0;
}

実行結果例

本の情報
本1:番号 1, タイトル C言語基礎, 価格 2200円, ジャンル 技術書
本2:番号 2, タイトル 配列入門, 価格 1800円, ジャンル 入門書
本3:番号 3, タイトル 関数のしくみ, 価格 2500円, ジャンル 参考書

解説

この問題では、構造体配列を使って複数の本の情報を管理しています。
code、title、price は初期化子で設定し、genre はあとから strcpy で代入しています。
文字列メンバには = で代入できないため、char型配列のメンバに対しては strcpy を使う必要があります。
また、配列と for文を組み合わせることで、3冊分のデータをまとめて表示できています。

実践問題

次の要件を満たすプログラムを作成してください。

① 三角形の情報を表す構造体を定義し、typedef 指定子で Triangle というエイリアスを与える。
 bottom:底辺の長さ(double型)
 height:高さ(double型)
② 構造体の配列を使って、5つの三角形の情報を管理する。
③ 初期化時に各三角形の値を以下のように設定する。
 {3.0, 4.0}, {5.0, 2.0}, {6.0, 7.0}, {8.0, 3.0}, {4.5, 5.0}
④ 各三角形の面積を計算し、一番面積の大きい三角形の情報を表示する。

解答例

ファイル名:15_4_3.c

#include <stdio.h>

// 三角形の情報を表す型
typedef struct {
    double bottom;   // 底辺
    double height;   // 高さ
} Triangle;

int main(void)
{
    Triangle triangles[5] = {
        {3.0, 4.0},
        {5.0, 2.0},
        {6.0, 7.0},
        {8.0, 3.0},
        {4.5, 5.0}
    };

    int max_index = 0;
    double max_area = triangles[0].bottom * triangles[0].height / 2.0;

    for (int i = 1; i < 5; i++) {
        double area = triangles[i].bottom * triangles[i].height / 2.0;
        if (area > max_area) {
            max_area = area;
            max_index = i;
        }
    }

    printf("一番面積の大きい三角形\n");
    printf("底辺:%.1f\n", triangles[max_index].bottom);
    printf("高さ:%.1f\n", triangles[max_index].height);
    printf("面積:%.1f\n", max_area);

    return 0;
}

実行結果例

一番面積の大きい三角形
底辺:6.0
高さ:7.0
面積:21.0

解説

この問題では、typedef を使って Triangle という型名を作っています。
そのおかげで Triangle triangles[5]; のように、自然な書き方で構造体配列を宣言できます。
また、最大面積を求める処理では、配列を順番に調べながら、現在の最大値より大きいものが見つかったら更新しています。
このように、構造体配列と繰り返し処理はとても相性がよいです。

実践問題

次の表で管理された商品の情報から、平均価格と平均在庫数を求めるプログラムを作成してください。

番号商品名価格在庫数
1ノート120.050
2ペン80.0120
3消しゴム60.070
4定規150.040
5ファイル200.030
6はさみ350.020

解答例

ファイル名:15_4_4.c

#include <stdio.h>

// 商品情報を表す構造体
typedef struct {
    int no;            // 番号
    char name[20];     // 商品名
    double price;      // 価格
    int stock;         // 在庫数
} Product;

int main(void)
{
    Product products[6] = {
        {1, "ノート", 120.0, 50},
        {2, "ペン", 80.0, 120},
        {3, "消しゴム", 60.0, 70},
        {4, "定規", 150.0, 40},
        {5, "ファイル", 200.0, 30},
        {6, "はさみ", 350.0, 20}
    };

    double total_price = 0.0;
    int total_stock = 0;

    for (int i = 0; i < 6; i++) {
        total_price += products[i].price;
        total_stock += products[i].stock;
    }

    printf("平均価格:%.1f円\n", total_price / 6);
    printf("平均在庫数:%.1f個\n", (double)total_stock / 6);

    return 0;
}

実行結果例

平均価格:160.0円
平均在庫数:55.0個

解説

この問題では、構造体配列に複数の商品の情報を入れ、for文で合計を求めています。
price は double 型なのでそのまま加算し、stock は int 型ですが、平均を小数で表示したいので最後に double に変換して計算しています。
構造体を使うことで、価格と在庫数という異なる型のデータを1件ずつまとまりとして扱えるのが大きな利点です。

実践問題

次の要件を満たすプログラムを作成してください。

① 構造体を用いて、1つの基準点と複数の点との距離を求めるプログラムを作成する。
② 各点は int 型の x 座標と y 座標で定義されている。
③ 基準点は変数で、比較用の複数の点は配列で管理する。
④ プログラムは、基準点と各点との距離を求めて表示する。
⑤ 距離の計算には次の式を使う。

=((x2x1)2+(y2y1)2)距離 = \sqrt{((x2 - x1)^2 + (y2 - y1)^2)}

⑥ 平方根を求めるには math.h の sqrt 関数を使用する。

解答例

ファイル名:15_4_5.c

#include <stdio.h>
#include <math.h>

// 点の情報を表す型
typedef struct {
    int x;   // x座標
    int y;   // y座標
} Point;

int main(void)
{
    Point origin = {0, 0};

    Point points[5] = {
        {3, 4},
        {5, 12},
        {-2, 8},
        {0, 7},
        {-6, -8}
    };

    printf("基準点:(%d, %d)\n\n", origin.x, origin.y);

    for (int i = 0; i < 5; i++) {
        double dx = points[i].x - origin.x;
        double dy = points[i].y - origin.y;
        double distance = sqrt(dx * dx + dy * dy);

        printf("点[%d]:(%d, %d)\n", i + 1, points[i].x, points[i].y);
        printf("→ 距離:%.3f\n\n", distance);
    }

    return 0;
}

実行結果例

基準点:(0, 0)

点[1]:(3, 4)
→ 距離:5.000

点[2]:(5, 12)
→ 距離:13.000

点[3]:(-2, 8)
→ 距離:8.246

点[4]:(0, 7)
→ 距離:7.000

点[5]:(-6, -8)
→ 距離:10.000

解説

この問題では、基準点を1つの構造体変数で表し、比較対象を構造体配列で表しています。
距離の計算式では平方根が必要なので sqrt 関数を使っています。
構造体を使うことで、x座標とy座標を「点」というひとつの単位で扱えるため、計算式の意味もはっきり見えます。
また、配列にしているので、複数の点を for文でまとめて処理できます。

宣言方法ごとの使い分けを整理する

ここまで学んだ内容を、最後に表で整理しておくと理解が安定します。

方法使いどころ
基本の構造体宣言struct point { ... };基本を理解するとき
型と変数を同時定義struct point { ... } p1;1か所でまとめたいとき
同時初期化struct point { ... } p1 = {10, 20};定義と初期化を一度に済ませたいとき
無名構造体struct { ... } p1;一度きりの用途
typedeftypedef struct { ... } Point;読みやすく簡潔にしたいとき

どの方法も文法としては使えますが、長く使うコードや、学習を進めていく中では typedef の便利さを実感する場面が多くなります。
構造体を「使える」だけでなく、「読みやすく書ける」ようになることが、この単元の大きなポイントです。