6日でできる 新C言語入門|構造体の総合演習

構造体は、関連するデータをまとめて扱うための強力な道具。
基本・配列・ポインタの演習を通して、C言語のデータ設計力をしっかり身につけよう。

C言語の構造体は、複数の関連するデータを1つのまとまりとして扱うための仕組みです。

たとえば、会員データを扱う場合、名前、年齢、ポイントを別々の変数で管理することもできます。しかし、それらは本来1人分の情報としてまとまっているデータです。このような関連する情報を1つにまとめて扱えるようにするのが構造体です。

構造体を使えるようになると、現実世界のデータをプログラムの中で表現しやすくなります。商品、学生、会員、書籍、在庫、成績など、複数の項目を持つデータを整理して扱えるようになります。

この記事では、構造体の総合演習として、1件分のデータを管理する基本、複数件のデータを構造体配列で管理する方法、構造体ポインタを使って関数内でデータを更新する方法を順番に解説します。

構造体は、単体で使うだけでなく、配列やポインタ、関数と組み合わせることで実用性が大きく広がります。ここでは、サンプルプログラムを通して、構造体を実際のデータ管理にどう活用するのかを確認していきましょう。

構造体演習で確認するポイント

構造体の演習では、次の3つの使い方をしっかり確認しておくことが大切です。

学習内容確認するポイント
構造体の基本複数のメンバを1つのデータとしてまとめる
構造体配列同じ形式のデータを複数件まとめて管理する
構造体ポインタ関数へアドレスを渡して元のデータを操作する

構造体を使うと、ただ変数を並べるだけではなく、意味のあるデータのまとまりを作れます。

たとえば、会員データなら、名前、年齢、ポイントを1つの構造体にまとめられます。商品データなら、商品名、在庫数、価格を1つの構造体にまとめられます。

このように、関連するデータを1つの型として扱えることが、構造体の大きな魅力です。

図:構造体は関連するデータを1つにまとめる

この図から分かること

この図から分かるのは、構造体が複数の関連データをまとめるための仕組みだということです。

name、age、pointを別々に管理することもできますが、構造体にまとめることで、1人分の会員データとして扱えるようになります。データのまとまりがはっきりすると、プログラムの意味も読み取りやすくなります。

1件分のデータを構造体で管理する

最初の演習では、1人分の会員データを構造体で管理します。

構造体の基本では、次の流れを確認します。

手順内容
構造体を定義するどのようなメンバを持つデータかを決める
構造体変数を宣言する実際に使うデータを作る
メンバに値を入れるドット演算子で各メンバへアクセスする
メンバを表示するprintfで構造体の中身を確認する

構造体は、まず型として定義します。
そのあと、その構造体型を使って変数を作ります。

会員データを表示するプログラム

次のプログラムでは、会員の名前、年齢、ポイントを構造体でまとめて管理します。

プロジェクト/ファイル名: Lesson66_1/main.c

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

struct member {
    char name[100];
    int age;
    int point;
};

int main(void) {
    struct member m1;

    // 構造体の各メンバへ値を設定する
    strcpy(m1.name, "高橋健太");
    m1.age = 32;
    m1.point = 450;

    // 構造体の内容を表示する
    printf("名前:%s 年齢:%d ポイント:%d\n", m1.name, m1.age, m1.point);

    return 0;
}

実行結果

名前:高橋健太 年齢:32 ポイント:450

このプログラムでは、memberという構造体を定義しています。

struct member {
    char name[100];
    int age;
    int point;
};

member構造体には、name、age、pointという3つのメンバがあります。

メンバ内容
namechar配列名前を保存する
ageint年齢を保存する
pointintポイントを保存する

main関数の中では、struct member型のm1を宣言しています。

struct member m1;

これにより、m1という変数の中に、name、age、pointという3つのデータを持てるようになります。

ドット演算子でメンバにアクセスする

構造体変数のメンバにアクセスするときは、ドット演算子を使います。

m1.age = 32;
m1.point = 450;

これは、m1のageメンバ、m1のpointメンバに値を代入しているという意味です。

書き方意味
m1.namem1のnameメンバ
m1.agem1のageメンバ
m1.pointm1のpointメンバ

構造体変数そのものに値を入れるのではなく、構造体の中にある各メンバに値を入れると考えると分かりやすいです。

文字列メンバにはstrcpyを使う

nameはchar配列です。

C言語では、char配列に文字列を代入演算子で直接入れることはできません。

そのため、文字列をコピーするためにstrcpyを使います。

strcpy(m1.name, "高橋健太");

strcpyを使うには、string.hを読み込む必要があります。

#include <string.h>

文字列メンバを持つ構造体では、strcpyを使う場面が多く出てきます。文字列の扱いは、構造体の演習でも重要なポイントです。

構造体を使うとデータの意味が分かりやすくなる

構造体を使わずに会員データを管理する場合、次のように変数を別々に用意することもできます。

char name[100];
int age;
int point;

この書き方でも動きますが、これらが1人分の会員データであることは、変数名やプログラムの流れから読み取る必要があります。

構造体を使うと、これらをstruct memberという1つの型としてまとめられます。

struct member m1;

このように書くことで、m1は会員1人分のデータであることが分かりやすくなります。

構造体配列で複数データを管理する

次の演習では、複数の商品データを構造体配列で管理します。

構造体は、1件分のデータを表すだけではありません。配列にすることで、同じ形式のデータを複数件まとめて扱えます。

たとえば、商品データを3件管理する場合、次のような構造体配列を作れます。

PRODUCT items[3];

このitemsには、3件分の商品データを保存できます。

要素内容
items[0]1件目の商品データ
items[1]2件目の商品データ
items[2]3件目の商品データ

各要素は構造体なので、items[i].nameやitems[i].stockのように、配列の添え字とドット演算子を組み合わせてメンバにアクセスします。

typedefで構造体名を扱いやすくする

構造体配列の演習では、typedefを使って構造体型に短い名前を付けます。

typedef struct {
    char name[100];
    int stock;
    int price;
} PRODUCT;

このように書くと、structを毎回書かずにPRODUCTという型名で構造体変数を宣言できます。

PRODUCT items[3];

typedefを使うと、構造体を通常の型のように扱いやすくなります。

複数の商品データを表示するプログラム

次のプログラムでは、3件分の商品データを構造体配列で管理し、for文で順番に表示します。

プロジェクト/ファイル名: Lesson66_2/main.c

#include <stdio.h>

typedef struct {
    char name[100];
    int stock;
    int price;
} PRODUCT;

int main(void) {
    PRODUCT items[3] = {
        { "ノート", 20, 180 },
        { "鉛筆", 50, 80 },
        { "消しゴム", 35, 120 }
    };

    // 構造体配列の内容を順番に表示する
    for (int i = 0; i < 3; i++) {
        printf("商品名:%s 在庫数:%d 価格:%d円\n",
            items[i].name, items[i].stock, items[i].price);
    }

    return 0;
}

実行結果

商品名:ノート 在庫数:20 価格:180円
商品名:鉛筆 在庫数:50 価格:80円
商品名:消しゴム 在庫数:35 価格:120円

このプログラムでは、PRODUCT型の構造体配列itemsを宣言しています。

PRODUCT items[3] = {
    { "ノート", 20, 180 },
    { "鉛筆", 50, 80 },
    { "消しゴム", 35, 120 }
};

各要素には、商品名、在庫数、価格が入っています。

配列要素namestockprice
items[0]ノート20180
items[1]鉛筆5080
items[2]消しゴム35120

for文では、iを0から2まで変化させながら、各要素のメンバを表示しています。

items[i].name
items[i].stock
items[i].price

構造体配列を使うと、同じ形式のデータをまとめて処理しやすくなります。

図:構造体配列で複数件のデータを管理する

この図から分かること

この図から分かるのは、構造体配列では、1つの配列要素が1件分のまとまったデータになるということです。

items[0]、items[1]、items[2]は、それぞれ商品1件分のデータです。各要素の中に、name、stock、priceがまとまっています。for文と組み合わせることで、複数件のデータを順番に処理できます。

構造体配列の初期化

構造体配列は、宣言と同時に初期化できます。

PRODUCT items[3] = {
    { "ノート", 20, 180 },
    { "鉛筆", 50, 80 },
    { "消しゴム", 35, 120 }
};

各行の波かっこの中に、構造体のメンバに入れる値を順番に書きます。

この場合、PRODUCT構造体のメンバは、name、stock、priceの順番で定義されています。

typedef struct {
    char name[100];
    int stock;
    int price;
} PRODUCT;

そのため、初期化の値も、商品名、在庫数、価格の順番で書きます。

初期化の値対応するメンバ
ノートname
20stock
180price

構造体の初期化では、メンバの順番と値の順番を合わせることが大切です。

構造体配列は一覧処理に向いている

構造体配列は、複数件のデータを同じ形式で扱うときに便利です。

たとえば、商品一覧、学生一覧、成績一覧、会員一覧などに向いています。

データの例構造体のメンバ例
商品一覧商品名、在庫数、価格
学生一覧名前、点数、評価
会員一覧名前、年齢、ポイント
書籍一覧書名、著者、価格

構造体配列にすると、for文でまとめて表示したり、合計を求めたり、条件に合うデータだけを探したりできます。

たとえば、在庫数が少ない商品を探す、点数が80点以上の学生を表示する、といった処理にも発展できます。

構造体ポインタで関数内からデータを更新する

最後の演習では、構造体ポインタを使って、関数内で元の構造体データを更新します。

構造体を関数に渡す方法には、値渡しとポインタ渡しがあります。

渡し方関数に渡るもの関数内の変更
値渡し構造体のコピー呼び出し元に反映されない
ポインタ渡し構造体のアドレス呼び出し元に反映される

関数の中で元のデータを書き換えたい場合は、構造体のアドレスを渡します。

struct member m1;

関数側では、構造体ポインタとして受け取ります。

void updatePrice(ITEM* item, int new_price)

構造体ポインタからメンバにアクセスするときは、アロー演算子を使います。

item->price = new_price;

関数で商品価格を更新するプログラム

次のプログラムでは、構造体ポインタを使って、関数内で商品の価格を更新します。

プロジェクト/ファイル名: Lesson66_3/main.c

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

typedef struct {
    char name[100];
    int price;
} ITEM;

void updatePrice(ITEM* item, int new_price) {
    // 構造体ポインタを使って価格を更新する
    item->price = new_price;
}

int main(void) {
    ITEM notebook;

    // 商品データを設定する
    strcpy(notebook.name, "ノート");
    notebook.price = 180;

    // 関数に構造体のアドレスを渡して価格を変更する
    updatePrice(¬ebook, 220);

    printf("商品名:%s 価格:%d円\n", notebook.name, notebook.price);

    return 0;
}

実行結果

商品名:ノート 価格:220円

このプログラムでは、ITEM構造体を定義しています。

typedef struct {
    char name[100];
    int price;
} ITEM;

ITEMには、商品名を表すnameと、価格を表すpriceがあります。

main関数では、ITEM型のnotebookを宣言しています。

ITEM notebook;

そのあと、nameとpriceに値を設定しています。

strcpy(notebook.name, "ノート");
notebook.price = 180;

価格を更新するために、updatePrice関数へnotebookのアドレスを渡しています。

updatePrice(¬ebook, 220);

&notebookは、notebookのアドレスです。
関数側では、ITEM* itemとして受け取ります。

void updatePrice(ITEM* item, int new_price)

関数内では、item->priceを使って、itemが指している構造体のpriceメンバを変更しています。

item->price = new_price;

この変更は、main関数側のnotebookに反映されます。

構造体ポインタでは->を使う

構造体変数のメンバにアクセスするときは、ドット演算子を使います。

notebook.price

構造体ポインタが指す先のメンバにアクセスするときは、アロー演算子を使います。

item->price

この違いは、構造体ポインタを使ううえでとても重要です。

対象書き方
構造体変数notebook.price
構造体ポインタitem->price
構造体ポインタを明示的に参照(*item).price

item->priceは、(*item).priceと同じ意味です。

ただし、構造体ポインタではitem->priceのほうが読みやすいため、よく使われます。

図:構造体ポインタで元のデータを更新する

この図から分かること

この図から分かるのは、構造体ポインタを使うと、関数内から呼び出し元の構造体を直接変更できるということです。

main関数側のnotebookのアドレスをupdatePrice関数に渡すことで、関数内のitemはnotebookを指します。そのため、item->priceを変更すると、notebook.priceの値も変わります。

構造体を関数で扱うときの考え方

構造体を関数で扱うときは、その関数がデータを変更するのか、それとも表示や参照だけを行うのかを考えると整理しやすくなります。

関数の目的おすすめの渡し方理由
データを表示するだけ値渡しまたはconst付きポインタ元データを変更しない
データを変更するポインタ渡し呼び出し元のデータを更新できる
大きな構造体を扱うポインタ渡しコピーを避けられる
複数メンバをまとめて更新するポインタ渡し1つのアドレスで全メンバにアクセスできる

学習段階では、構造体を関数内で変更したい場合は、アドレスを渡してポインタで受け取る、と覚えると分かりやすいです。

表示だけなら値渡しでもよいですが、大きな構造体ではコピーが発生するため、ポインタ渡しを使うことも多くあります。

3つの演習で身につくこと

今回の3つの演習では、構造体の基本から応用までを段階的に確認しました。

演習内容身につくこと
Lesson66_11件分の会員データを管理構造体定義、構造体変数、ドット演算子
Lesson66_2複数の商品データを管理typedef、構造体配列、for文での一覧処理
Lesson66_3関数で商品価格を更新構造体ポインタ、アロー演算子、ポインタ渡し

構造体の学習では、まず1件分のデータをまとめる基本を理解します。
次に、構造体配列で複数件のデータを管理できるようにします。
最後に、構造体ポインタを使って、関数から元のデータを変更できるようにします。

この順番で学ぶと、構造体の使い道が自然に広がっていきます。

構造体演習で注意したいポイント

構造体を使うときは、次の点に注意しましょう。

文字列メンバにはstrcpyを使う

char配列のメンバに文字列を入れるときは、strcpyを使います。

strcpy(m1.name, "高橋健太");

文字列を扱うためには、string.hを読み込んでおきます。

構造体変数には.を使う

構造体変数のメンバへアクセスするときは、ドット演算子を使います。

m1.age

items[i].price

構造体ポインタには->を使う

構造体ポインタからメンバへアクセスするときは、アロー演算子を使います。

item->price

配列では添え字とメンバ名の順番を意識する

構造体配列では、まず配列の要素を選び、そのあとメンバにアクセスします。

items[i].stock

これは、itemsのi番目の構造体にあるstockメンバという意味です。

関数で変更したいならアドレスを渡す

関数の中で構造体の元データを変更したい場合は、&を使って構造体変数のアドレスを渡します。

updatePrice(¬ebook, 220);

関数側では、構造体ポインタで受け取ります。

void updatePrice(ITEM* item, int new_price)

構造体を使うと現実的なデータを表現しやすくなる

構造体は、現実世界のデータをC言語の中で表現するためにとても役立ちます。

会員データ、商品データ、学生データ、在庫データのように、複数の項目を持つ情報は構造体に向いています。

さらに、構造体配列を使えば、同じ形式のデータを複数件まとめて管理できます。構造体ポインタを使えば、関数内から元のデータを更新したり、大きな構造体のコピーを避けたりできます。

構造体を使いこなせるようになると、単純な変数だけでは表しにくかったデータを、意味のあるまとまりとして設計できるようになります。

構造体演習を次の学習につなげる

構造体の基本、構造体配列、構造体ポインタを理解すると、C言語で扱えるデータの幅が大きく広がります。

たとえば、次のようなプログラムに発展できます。

発展例内容
成績管理プログラム学生名、点数、平均点、評価を管理する
在庫管理プログラム商品名、在庫数、価格を管理する
住所録プログラム名前、電話番号、メールアドレスを管理する
ファイル読み込み処理読み込んだデータを構造体配列に保存する
リスト構造構造体ポインタでデータ同士をつなぐ

構造体は、C言語のデータ設計の土台になる機能です。
1件分のデータをまとめる、複数件を配列で扱う、関数にポインタで渡して操作する。この3つを押さえておくと、より実践的なプログラムへ進みやすくなります。

今回の演習を通して、構造体を単なる文法として覚えるのではなく、データを分かりやすく整理するための道具として使えるようにしていきましょう。