C言語基礎|構造体の基礎

「バラバラの情報を、ひとつの“まとまり”に。Cの構造体でデータ設計が一気にラクになる!」

C言語でプログラムを書いていると、関連する情報をセットで扱いたい場面がよく出てきます。
たとえば「商品」なら、商品名・価格・在庫数…みたいに、1つの対象に複数の項目がぶら下がるんですよね。

もし構造体が無いと、商品名は配列、価格は別の変数、在庫数も別の変数…と散らばってしまって、
「この3つって同じ商品だっけ?」が分かりにくくなります。

そこで登場するのが 構造体(structure)
“関連データをひとまとめにして、1つの型として扱える”のが、構造体のいちばん大事な価値です。

構造体って何?(カード形式のイメージ)

構造体は、「項目がそろったカードのフォーマット」を作る感覚に近いです。

図:構造体は「フォーマット」/変数は「実体」

(1) フォーマット(型)
struct Product {
  name
  price
  stock
};

(2) 実体(変数)
struct Product apple;
struct Product banana;
  • 上は「こういう項目を持つデータの形」を定義しているだけ(まだ中身は無い)
  • 下で初めて「その形を持つデータ(変数)」が作られる

用語の整理(タグ・メンバ・オブジェクト)

まずは言葉をスッキリさせましょう。

用語何を指す?
構造体タグ(tag)struct の後ろに書く名前(型を識別する)struct Product の Product
メンバ(member)構造体の中の各項目name, price, stock
オブジェクト(変数)実際に作った“中身を入れられる”実体struct Product apple;

補足(ここ、試験でも混乱しやすい)

  • 型名は struct Product(struct とタグ名がセット)
  • Product だけは「タグ名」であって、単体では型名ではありません(typedef を使うと話が変わりますが、今は基本だけでOKです)

構造体の宣言(型の作り方)

書式(型を作る)

struct タグ名 {
    型 メンバ名;
    型 メンバ名;
    ...
};

この宣言は何をするの?

  • { } の中に並んだメンバをまとめて
    struct タグ名 という新しい型を作ります。
  • 末尾の ; は必須です(ここ、うっかりミス多いです)

オブジェクト(変数)の宣言・定義

書式(実体を作る)

struct タグ名 変数名;

これは何をするの?

  • その型(struct タグ名)を持つ 変数(オブジェクト) を作ります。
  • 変数なので、あとから値を入れたり、取り出したりできます。

型とオブジェクトを一度に定義する

書式(型定義 + 変数定義をまとめて)

struct タグ名 {
    ...
} a, b;

これは何をするの?

  • 型を定義すると同時に、その型の変数 a と b も作ります。

タグ名を省略する形もある(注意点あり)

struct {
    ...
} a, b;
  • この場合、型に名前が付かないので、別の場所で同じ型の変数を追加定義できなくなります
  • 「その場限りの型」でOKなときだけ使うのが安全です

メンバアクセス(ドット演算子)

構造体の中身(メンバ)に触るには ドット演算子 を使います。

表記意味
obj.mem構造体変数 obj のメンバ mem を表す

書式

オブジェクト名.メンバ名

apple.price

これは「価格」という double型の値そのものを表すので、普通の変数と同じように代入・参照できます。

サンプルプログラム(代入して表示する例)

※元の “学生/身体検査” とは別の、よりシンプルな例に置き換えています。表示メッセージも別の日本語にしています。

プロジェクト名:chap12-2-1 ソースファイル名:chap12-2-1.c

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

#define NAME_LEN 32

// 商品を表す構造体
struct Product {
    char   name[NAME_LEN];  // 商品名
    int    stock;           // 在庫数
    double price;           // 価格
};

int main(void)
{
    struct Product item;

    strcpy(item.name, "りんご");
    item.stock = 12;
    item.price = 158.0;

    printf("商品名:%s\n", item.name);
    printf("在庫:%d 個\n", item.stock);
    printf("価格:%.0f 円\n", item.price);

    return 0;
}

このプログラムで出てくる「命令(要素)」の役割

要素何をする?
#include <stdio.h>printf を使えるようにする
#include <string.h>strcpy を使えるようにする
#define NAME_LEN 32配列サイズの定数を用意する(変更に強い)
struct Product { ... };商品の“型”を作る
struct Product item;その型の変数 item を作る
strcpy(item.name, "りんご");文字列を char配列にコピーする(代入はできないため)
item.stock = 12;intメンバへ代入
item.price = 158.0;doubleメンバへ代入
printf(...)画面に表示する

メンバの初期化(作るときにまとめて入れる)

構造体も、変数を作るときに初期化できます。
配列と似ていて、{ } に 宣言順で 値を並べます。

サンプルプログラム(初期化子の例:一部省略すると 0 になる)

プロジェクト名:chap12-2-2 ソースファイル名:chap12-2-2.c

#include <stdio.h>

#define NAME_LEN 32

struct Product {
    char   name[NAME_LEN];
    int    stock;
    double price;
};

int main(void)
{
    // price を書いていない → 自動的に 0.0 になる
    struct Product item = {"みかん", 30};

    printf("商品名:%s\n", item.name);
    printf("在庫:%d 個\n", item.stock);
    printf("価格:%.1f 円\n", item.price);

    return 0;
}

初期化子のルールまとめ

ルール内容
並べる順番メンバの宣言順(name → stock → price)
書き方{ 値, 値, 値 }
省略したメンバ0(数値なら 0、double なら 0.0、配列は要素が 0 で埋まる)
最後にカンマ付けてもよい(スタイル次第)

図で理解:構造体メモリ配置のイメージ

構造体のメンバは、基本的に宣言順に並ぶイメージでOKです。
(実際にはアラインメントの都合で“すき間”が入ることがあります)

図:宣言順に並ぶ(イメージ)

図の説明(大事ポイント)

  • 先頭のメンバほど小さいアドレスに置かれやすい。
  • 末尾のメンバほど大きいアドレスに置かれやすい。
  • ただし型によっては、CPUが扱いやすい配置にするために、間に空き領域(パディング)が入ることがある。

演習問題

演習12-1

上の「初期化するプログラム」をもとに、変数 item の各メンバのアドレスを表示するプログラムを作成してください。
表示するのは name, stock, price の3つです。

解答例(そのまま動く版)

プロジェクト名:chap12-2-3 ソースファイル名:chap12-2-3.c

#include <stdio.h>

#define NAME_LEN 32

struct Product {
    char   name[NAME_LEN];
    int    stock;
    double price;
};

int main(void)
{
    struct Product item = {"みかん", 30};

    printf("name の先頭アドレス:%p\n", (void*)item.name);
    printf("stock のアドレス:%p\n", (void*)&item.stock);
    printf("price のアドレス:%p\n", (void*)&item.price);

    return 0;
}

解説(ここで出てくる命令の意味)

要素何をする?
&item.stockstock というメンバのアドレスを取る
&item.priceprice というメンバのアドレスを取る
item.namename は配列なので、式の中では先頭要素へのポインタのように扱われやすい
%pアドレス表示用の書式指定
(void*)%p に渡すための型変換(お作法)