C言語基礎|12章のまとめ

「構造体が分かると、C言語が“現実の形”で書ける。12章で身につけた設計力を総整理!」

12章では、構造体を通して「データをどうまとめるか」「どう扱うか」を、かなり実戦的に学びました。
ただ文法を覚えるだけじゃなくて、現実世界のまとまりを、プログラムに素直に投影するという考え方が身についたはずです。

このまとめでは、12章で登場した重要ポイントを、表や図で整理しながら「なぜそうなるのか」までしっかり確認します。
最後に、まとめ用のサンプルも“別のシンプル例”に差し替えて、もう一度スッキリ復習できるようにしますね。

12章で押さえた「派生型」5種類

基本型(int や double など)を素材にして、C言語は色々な型を組み立てられます。これが派生型です。

派生型の種類と代表的な書式

派生型代表的な書式何を表す?
配列型Type a1[n];同じ型の要素が並ぶ集合
構造体型struct { Type m1; Type m2; } a2;異なる型をひとまとめにする集合
共用体型union { Type m1; Type m2; } a3;同じ領域を共有してメンバを切り替える
関数型Type a4(Type p1, Type p2) { ... }引数と戻り値で決まる型
ポインタ型Type *a5;どこかのオブジェクトや関数を指す型

図:派生型は「素材から組み立てる」

図の説明
C言語は「型を組み合わせて設計する」言語です。構造体はその中心的な道具でした。

現実世界を投影するときのコツは「まとまりのまま」

現実のモノはたいてい、情報がセットです。
それをバラすと、管理が難しくなりがちです。

投影の方針

方針うれしさ
まとまりを保つ人(名前・誕生日)を1つの構造体にする関連が壊れにくい、読みやすい
ばらばらにする名前配列と誕生日配列を別々にするズレやすい、修正がつらい

メンバと構成メンバ(入れ子の見方)

構造体の中には、基本型だけでなく配列や構造体を置けます。
このとき、分解して見たくなるのが「構成メンバ」です。

図:メンバと構成メンバの違い(イメージ)

呼び方の整理

用語意味
メンバ構造体の直下に並ぶ部品
構成メンバそれ以上分解できない最小単位の部品

記憶域への並びは「宣言順」

構造体のメンバは、基本的に宣言した順番でメモリに並びます(細部はアライメントの影響あり)。

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

図の説明
構造体は「カードのフォーマット」のようなもので、項目(メンバ)が宣言順に配置されます。

タグ名と typedef 名の違い

構造体の型名は、タグ名の有無や typedef によって変わります。ここは混乱しやすいので表で整理します。

タグ名と typedef の関係

宣言型名として使える呼び方特徴
struct student { ... };struct studentタグ名を使う、2単語
typedef struct student { ... } Student;struct student と Student同義語を追加できる
typedef struct { ... } Student;Studentタグがないので struct student は使えない

初期化子のルール(省略は0になる)

構造体の初期化は、メンバの順に値を並べて {} で囲みます。

書式

型名 変数 = { 初期化子1, 初期化子2, ... };

初期化のポイント

ルール内容
メンバ順宣言順に対応
省略省略されたメンバは 0 で初期化
最後のカンマ付けてもよい(読みやすさに便利)

メンバアクセス演算子 . と ->

構造体を扱うときの最重要アイテムです。

. と -> の使い分け

対象記法
構造体変数.obj.mem
構造体へのポインタ->p->mem
ポインタで . を使う場合(*p).mem(*p).mem(括弧が必要)

p->mem は (*p).mem の短縮

p -> mem   ≒   (*p).mem

図の説明
-> は「ポインタが指す構造体のメンバへアクセス」を短く書くための演算子です。

配列と構造体は「集成体型」

複数データの集まりという意味で、配列と構造体は仲間扱いされます。

集成体型の共通点と違い

観点配列構造体
集まり同じ型の集合異なる型の集合が得意
添字アクセスできるできない(メンバ名でアクセス)
代入できない同じ型ならできる

構造体は代入できる(ここが強い)

構造体同士の代入は「全メンバをまとめてコピー」です。

構造体代入のイメージ

説明
個別に name だけ、birthday だけ…とコピーを書く必要が減るので、実務でもミスが減ります。

関数は配列を返せないが、構造体は返せる

配列は代入できないので、戻り値として返す形が作れません。
構造体は代入できるので戻り値にできます。

戻り値にできる?

種類関数の戻り値理由
配列そのままは不可代入できないため
構造体可能値として返して代入できるため

名前空間は4種類(同名でも衝突しないことがある)

Cには「同じ綴りでも役割が違えばOK」という仕組みがあります。

名前空間の4分類

名前空間どこで使う?
ラベル名start:goto の飛び先
タグ名struct Xstruct/union/enum のタグ
メンバ名obj.x構造体の中
一般的な識別子変数名/関数名ふだんの名前

まとめ用サンプル

社員名簿(社員と入社日)を構造体で表現するプログラム例です。

仕様

  • Date 構造体(年・月・日)
  • Employee 構造体(名前・入社日 Date)
  • 今日の日付を入力して表示
  • 社員一覧を -> で表示(ポインタ引数)

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

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define NAME_LEN 64

struct Date {
    int y;
    int m;
    int d;
};

typedef struct {
    char name[NAME_LEN];
    struct Date joined;
} Employee;

void print_Employee(const Employee *e)
{
    printf("%s(入社日:%04d/%02d/%02d)\n",
           e->name, e->joined.y, e->joined.m, e->joined.d);
}

int main(void)
{
    struct Date today;

    Employee list[] = {
        {"田中", {2021, 4, 1}},
        {"鈴木", {2022, 10, 15}},
        {"佐藤", {2023, 7, 20}},
    };

    printf("今日の日付を入力してください。\n");
    printf("年:"); scanf("%d", &today.y);
    printf("月:"); scanf("%d", &today.m);
    printf("日:"); scanf("%d", &today.d);

    printf("入力を確認しました:%d年%d月%d日\n", today.y, today.m, today.d);

    printf("--- 社員一覧 ---\n");
    for (int i = 0; i < (int)(sizeof(list) / sizeof(list[0])); i++) {
        print_Employee(&list[i]);
    }

    return 0;
}

サンプル内の命令(要素)の書式と役割

struct Date { ... };

要素書式何をする?
構造体宣言struct タグ名 { ... };型(フォーマット)を作る

typedef struct { ... } Employee;

要素書式何をする?
typedeftypedef struct { ... } 型名;型名を1単語で使えるようにする

print_Employee(const Employee *e)

要素書式何をする?
ポインタ引数const Employee *eEmployee を参照だけする(中身を変えない)
->e->joined.yポインタが指す構造体のメンバにアクセス

sizeof(list) / sizeof(list[0])

要素書式何をする?
sizeofsizeof(対象)対象のバイト数を得る
要素数sizeof(list)/sizeof(list[0])配列の要素数を計算する

12章のポイントを一枚で整理(チェックリスト)

最終チェック

覚えたいことひとことで
構造体は“まとまり”現実の形でデータを扱える
. と ->変数なら .、ポインタなら ->
初期化 {}メンバ順、省略は0
typedef型名を1単語にできる
構造体は代入できる全メンバがまとめてコピー
構造体は返せる関数の戻り値にできる
配列は返せないそのままの戻り値は不可
名前空間役割が違えば同名でもOK