C言語基礎|関連データをまとめる構造体

「バラバラ管理、もう限界。構造体で“ひとまとまり”にして、ミスを消そう。」

ポインタや文字列を学んできたところで、次の“山場”が構造体です。
でも怖がらなくて大丈夫。構造体は一言でいうと 「同じ対象に属するデータを、1つの箱にまとめて扱うしくみ」 です。

たとえば「社員」という対象があるなら、

  • 社員ID(int)
  • 氏名(char 配列)
  • 部署(char 配列)
  • 評価点(int)

みたいに、型が違うデータがセットで付いてきますよね。
これを「配列を何本も作って、同じ添字で対応付ける」方式で管理すると、規模が増えた瞬間に事故ります。

  • 交換(swap)を1本だけ忘れて、データがズレる
  • 添字の意味が分からなくなる
  • 修正のたびに、修正箇所が増殖する

そこで「最初から“関連している”ことをプログラム上でも表現しよう」というのが、構造体の出番です。

まずは「配列を複数持つ管理」がなぜ危ないのか

ありがちな管理方法(ズレやすい)

「商品」を管理するとして、別々の配列を用意する例です。

データ管理方法
商品名"りんご"文字列names[i]
価格120intprices[i]
在庫30intstocks[i]

この方式のルールは「同じ i が同じ商品」という暗黙の約束。
でも、ソートや入れ替えをするときに 1つでも交換を忘れたら破綻します。

図:添字の対応がズレると何が起きる?

ソート前(対応が正しい)

inames[i]prices[i]stocks[i]
0りんご12030
1みかん8050
2ばなな15020

価格でソートしたいのに、prices だけ入れ替えた(事故)

inames[i]prices[i]stocks[i]
0りんご8030
1みかん12050
2ばなな15020

もうこの時点で「りんごの価格が80円」みたいな嘘データになります。
こういう“整合性崩壊”が、配列分割管理の最大の弱点です。

解決策:構造体で「1件のデータ」を1つにまとめる

構造体は「関連データをまとめた新しい型」を作れます。

構造体は「1レコード=1箱」

  • 商品1件を struct Product にまとめる。
  • 配列にするなら Product products[個数] とできる。
  • 入れ替えは「箱ごと」交換すれば整合性が保てる。
1箱(1商品)中身
Productname / price / stock

サンプルプログラム:構造体で商品一覧を価格順に並べ替える(バブルソート)

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

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

#define COUNT    5
#define NAME_LEN 32

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

// 2つのProductを交換する
void swap_product(Product *a, Product *b)
{
    Product temp = *a;
    *a = *b;
    *b = temp;
}

// priceの昇順で並べ替える(バブルソート)
void sort_by_price(Product items[], int n)
{
    for (int i = 0; i < n - 1; i++) {
        for (int j = n - 1; j > i; j--) {
            if (items[j - 1].price > items[j].price) {
                swap_product(&items[j - 1], &items[j]);
            }
        }
    }
}

int main(void)
{
    Product items[COUNT] = {
        {"りんご",   120, 30},
        {"みかん",    80, 50},
        {"ばなな",   150, 20},
        {"ぶどう",   300, 10},
        {"もも",     200, 15}
    };

    puts("現在の一覧です。");
    for (int i = 0; i < COUNT; i++) {
        printf("%d: %-6s  価格=%3d円  在庫=%2d\n",
               i + 1, items[i].name, items[i].price, items[i].stock);
    }

    sort_by_price(items, COUNT);

    puts("\n価格の安い順に並べ替えました。");
    for (int i = 0; i < COUNT; i++) {
        printf("%d: %-6s  価格=%3d円  在庫=%2d\n",
               i + 1, items[i].name, items[i].price, items[i].stock);
    }

    return 0;
}

この例のポイント(何が嬉しい?)

  • 並べ替えの交換は swap_product 1回だけ
    → 名前・価格・在庫が 必ずセットで動く
  • 「同じ添字が同じ商品」という暗黙ルールが薄れる
    → items[i] が「商品1件」そのもの

文書中の「各項目」を表と図でしっかり解説

➀ データの関連性とは?

“同じ対象に属するデータ同士のつながり”です。
学生なら「名前・身長・体重」、商品なら「商品名・価格・在庫」みたいに、一緒に動くべき情報のこと。

対象関連データの例一緒に動く理由
学生名前 / 身長 / 体重同一人物の属性だから
商品商品名 / 価格 / 在庫同一商品の属性だから
社員社員番号 / 部署 / 評価同一社員の属性だから

説明(表の意味)
左列の「対象」が“主語”で、真ん中が“持っている属性”。右列が“なぜセットか”です。ここが腹落ちすると、構造体の必要性が自然に見えてきます。

➁ バブルソートがやっていること(隣同士を比較して必要なら交換)

本文にあった「赤色の部分=隣り合う2要素の判定と交換」は、バブルソートの核です。

手順何をする?目的
比較items[j-1] と items[j] の価格を比べる順序が正しいか確認
交換逆なら2つを入れ替える小さい値を前へ送る
繰り返し右から左へ、何度も行う全体を整列させる

説明(表の意味)
比較→交換→繰り返し、が“泡(bubble)”みたいに小さい値が左へ浮かんでいく動きになります。

➂ 「交換を関数に依頼する」意味(swap の重要性)

交換処理はミスが起きやすいので、関数にまとめると安全です。
配列を複数持つ方式だと、swap_int / swap_str / swap_double…と増殖していきます。

管理方式交換に必要なもの増えるとどうなる?
配列を別々に管理型ごとのswap関数、交換の呼び出し漏れ対策漏れた瞬間に整合性崩壊
構造体でまとめるswap_product 1つ交換漏れが起きにくい

説明(表の意味)
「関数が増える」のが問題というより、「交換を忘れる余地」が増えるのが本質的な問題です。構造体は“忘れる余地”そのものを減らします。

登場する命令(ここでは関数)の書式と役割

※C言語の文脈では「命令=関数や構文」として扱い、ここで出てきたものを整理します。

swap_product の書式と役割

項目内容
書式void swap_product(Product *a, Product *b)
役割2つのProduct(商品データ)を丸ごと入れ替える
ポイントポインタで受け取るので、呼び出し元の配列要素が直接入れ替わる

説明
a と b は「商品の箱」の住所です。中身を temp に退避して入れ替えています。箱ごと動くので、関連データが絶対にズレません。

sort_by_price の書式と役割

項目内容
書式void sort_by_price(Product items[], int n)
役割items を価格の昇順に並べ替える
アルゴリズムバブルソート
比較条件items[j - 1].price > items[j].price

説明
items[j].price のように、構造体の中身へは . でアクセスします。配列要素そのものを交換するので、データ整合性を保ったまま並べ替えられます。

よく出る記号もここで整理(. と &)

記号意味
.items[i].price構造体のメンバにアクセス
&&items[j]変数のアドレス(住所)を取得

説明
交換関数は Product * を受け取るので、配列要素の住所を & で渡します。これが「呼び出し元を書き換える」基本テクです。