C言語のきほん|構造体配列で複数データを扱う

1人分の情報から、複数人分の管理へ。構造体配列でデータ整理がぐっと実用的になる。

前の内容では、構造体を使って「1人分の情報」や「1件分のデータ」をひとまとまりとして扱う方法を学びました。
構造体を使うことで、学籍番号、名前、点数のような異なる型のデータを、ひとつの意味ある単位として整理できるようになります。

ただ、実際のプログラムでは、1件だけを扱うことはあまり多くありません。
たとえば、クラス全員の成績、商品一覧、社員名簿、図書データなど、同じ形式のデータを何件もまとめて扱いたい場面がたくさんあります。

そんなときに便利なのが、構造体の配列です。
構造体の配列を使うと、同じ型の構造体を複数個まとめて並べて管理できます。
つまり、「1人分の学生情報」を表す構造体を、配列として何人分も持てるようになるわけです。

構造体だけでも便利でしたが、配列と組み合わせることで実用性が一気に高まります。
ここをしっかり理解しておくと、あとで学ぶ繰り返し処理、検索、並べ替え、関数への受け渡しなどにも自然につながっていきます。
今回は、構造体配列の基本、初期化の方法、配列要素へのアクセス、for文との組み合わせ方まで、やさしく順番に見ていきましょう。

構造体の配列とは何か

構造体は、異なる型のデータをひとまとめにしたものでした。
そして配列は、同じ型のデータを並べてまとめて扱う仕組みでした。

この2つを組み合わせたものが、構造体の配列です。

たとえば、学生1人分の情報を表す構造体があるとします。

  • 学籍番号
  • 氏名
  • 平均点

これを1人分だけでなく、3人分、10人分、100人分とまとめて扱いたいとき、構造体の配列を使います。

つまり、構造体の配列は、

  • 1つ1つの要素は構造体
  • その構造体が配列として並んでいる

という形になります。

構造体だけの場合と構造体配列の場合の違い

まずは違いを見てみましょう。

構造体だけを使う場合

struct student s1;
struct student s2;
struct student s3;

このように1人ずつ別々の変数名を付けて管理することはできます。
ただし、人数が増えると変数名をたくさん用意しなければならず、繰り返し処理とも相性がよくありません。

構造体の配列を使う場合

struct student students[3];

このように書けば、3人分の学生情報をまとめて管理できます。
配列なので、students[0]、students[1]、students[2] のように添字で順番に扱えます。

このおかげで、for文を使って全員分をまとめて処理できるようになります。

構造体配列のイメージをつかむ

構造体配列は、見た目としては「大きな棚の中に、同じ形の引き出しがいくつも並んでいる」ようなイメージです。
1つの引き出しが1件分のデータで、その中にはさらに複数の項目があります。

たとえば学生情報なら、

  • students[0] の中に id、name、average
  • students[1] の中に id、name、average
  • students[2] の中に id、name、average

というように、同じ構成のデータが並びます。

この図では、配列の各要素が単なる整数や文字ではなく、構造体そのものであることが分かります。
つまり、students[1] は1つの値ではなく、「学籍番号・氏名・平均点」という複数の情報を持った1件分のデータです。
構造体配列は、この「1件分のデータ」を複数並べて管理する仕組みだと考えると理解しやすいです。

構造体配列の宣言と初期化

構造体配列は、普通の配列と同じように宣言できます。
違うのは、配列の要素の型が構造体であることです。

基本の書き方は次の通りです。

struct 構造体タグ名 配列名[要素数];

たとえば student型の構造体を3件分持つなら、次のように書きます。

struct student students[3];

さらに、宣言と同時に初期化することもできます。

struct student students[3] = {
    {101, "田中太郎", 92.0},
    {102, "佐藤花子", 88.5},
    {103, "鈴木一郎", 79.5}
};

ここでは、students[0]、students[1]、students[2] に対して、それぞれ1件ずつ構造体の初期値を与えています。

サンプルプログラムで確認する

シンプルな図書情報の例に解説します。

ファイル名:15_2_1.c

#include <stdio.h>

// 図書情報を表す構造体の型を宣言
struct book {
    int code;          // 図書番号
    char title[30];    // 書名
    double price;      // 価格
};

int main(void)
{
    // 構造体配列を初期化
    struct book books[3] = {
        {1, "C言語入門", 2200.0},
        {2, "アルゴリズム基礎", 2800.0},
        {3, "プログラム設計", 2500.0}
    };

    // 構造体配列の内容を順番に表示
    for (int i = 0; i < 3; i++) {
        printf("図書%d: %d %s %.0f円\n",
            i + 1,
            books[i].code,
            books[i].title,
            books[i].price);
    }

    return 0;
}

実行結果例

図書1: 1 C言語入門 2200円
図書2: 2 アルゴリズム基礎 2800円
図書3: 3 プログラム設計 2500円

このプログラムでは、3冊分の図書情報を構造体配列 books にまとめて保存しています。
そして for文を使って、全要素を順番に表示しています。

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

まず、構造体の型を宣言しています。

struct book {
    int code;
    char title[30];
    double price;
};

この構造体は、1冊分の図書情報を表しています。

  • code は図書番号
  • title は書名
  • price は価格

です。

次に、その構造体を要素にもつ配列を宣言しています。

struct book books[3] = {
    {1, "C言語入門", 2200.0},
    {2, "アルゴリズム基礎", 2800.0},
    {3, "プログラム設計", 2500.0}
};

ここでの books は、3個の構造体を並べて持つ配列です。
それぞれの初期値は次のように対応します。

配列要素codetitleprice
books[0]1C言語入門2200.0
books[1]2アルゴリズム基礎2800.0
books[2]3プログラム設計2500.0

このように、1つの配列要素ごとに1件分の図書データが入っています。

配列要素の構造体メンバにアクセスする

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

書き方は次の通りです。

配列名[添字].メンバ名

たとえば、

books[0].code
books[1].title
books[2].price

のように書きます。

これは、

  • books[0] の中の code
  • books[1] の中の title
  • books[2] の中の price

を表しています。

この形はとても大事です。
配列と構造体が合わさると少し長く見えますが、意味を分けて考えると難しくありません。

分けて考えると分かりやすい

部分意味
books[1]配列の2番目の要素である構造体
books[1].titleその構造体のtitleメンバ

つまり、配列で何件目かを選んでから、構造体の中の項目を取り出すという流れです。

for文と構造体配列はとても相性がよい

構造体配列の大きな利点は、for文でまとめて処理できることです。

今回のサンプルでは、次のようにしています。

for (int i = 0; i < 3; i++) {
    printf("図書%d: %d %s %.0f円\n",
        i + 1,
        books[i].code,
        books[i].title,
        books[i].price);
}

もし構造体配列を使わず、book1、book2、book3 のように別々の変数にしていたら、同じような printf を何回も書かなければなりません。
しかし配列にしておけば、繰り返し処理でまとめて扱えます。

これは実務でもとても大切な考え方です。
データが複数件あるなら、配列や繰り返し処理で整理して扱う方が、プログラムがすっきりして読みやすくなります。

メモリ上ではどう並ぶのか

構造体配列を宣言すると、メモリ上には同じ型の構造体が連続して並びます。
イメージとしては、1件分の構造体が3つ連続して保存されている状態です。

この図を見ると、構造体配列は「構造体がひとつだけある」のではなく、同じ構造体が連続して並んでいることがよく分かります。
配列である以上、添字を使って0番目、1番目、2番目というように順番にアクセスできます。
そして各要素の中には、さらに複数のメンバが入っています。
この二重の構造を理解することが、構造体配列を使いこなす第一歩です。

構造体配列の初期化の見方

初期化の書き方は、最初は少し入れ子が深く感じるかもしれません。
でも、外側の波括弧は配列全体、内側の波括弧は各構造体1件分、と考えると分かりやすいです。

struct book books[3] = {
    {1, "C言語入門", 2200.0},
    {2, "アルゴリズム基礎", 2800.0},
    {3, "プログラム設計", 2500.0}
};

これを分解して考えると、

  • 配列 books の中に3つの要素がある
  • 1つ目の要素に {1, "C言語入門", 2200.0}
  • 2つ目の要素に {2, "アルゴリズム基礎", 2800.0}
  • 3つ目の要素に {3, "プログラム設計", 2500.0}

という意味になります。

1件ずつ個別に代入することもできる

もちろん、初期化子でまとめて書くだけでなく、後から1件ずつメンバへ代入することもできます。

books[0].code = 1;
books[0].price = 2200.0;

ただし、文字列メンバに対しては、前回学んだとおり = では代入できません。
char型配列のメンバに文字列を入れる場合は strcpy を使います。

たとえば次のようになります。

#include <string.h>

books[0].code = 1;
strcpy(books[0].title, "C言語入門");
books[0].price = 2200.0;

構造体配列でも、文字列メンバの扱いは普通の構造体と同じです。

構造体配列を使うメリット

構造体配列を使うと、複数件のデータ管理がとても整理しやすくなります。

主なメリット

利点内容
複数件のデータをまとめて扱える学生一覧、商品一覧、社員一覧などに向いている
for文と組み合わせやすい全件表示や集計がしやすい
データの意味がはっきりする1件分の項目がまとまっている
プログラムがすっきりする変数を何個も作る必要がない

たとえば、学生30人分の情報を扱うのに、student1、student2、student3 … と変数を30個並べるのは大変です。
でも、

struct student students[30];

と書けば、一気に整理できます。

よくあるつまずきポイント

構造体配列では、初心者が混乱しやすいポイントがいくつかあります。

配列の添字とメンバ名の順番を逆にしてしまう

正しい書き方は次の通りです。

books[0].title

先に配列要素を選んで、そのあとメンバにアクセスします。

配列全体に対してメンバ名を書こうとしてしまう

次のような書き方はできません。

books.title

books は配列全体なので、直接 title メンバは持っていません。
まずどの要素なのかを選ぶ必要があります。

文字列メンバに = で代入しようとしてしまう

これもよくあるミスです。

books[0].title = "C言語入門";

この書き方はできません。
char型配列なので strcpy を使います。

普通の配列との違いも整理しておく

最後に、普通の配列と構造体配列の違いを表で整理しておくと、理解がさらに安定します。

種類配列の1要素に入るもの
int型配列整数1つscores[0]
double型配列小数1つprices[0]
構造体配列複数メンバを持つ構造体1つstudents[0]

つまり、構造体配列の1要素は単純な1つの数値ではなく、まとまった1件分のデータです。
ここがいちばん大切なポイントです。

この段階で押さえておきたいポイント

ここまでで、ぜひ押さえておきたいポイントを整理します。

ポイント内容
構造体配列とは同じ型の構造体を複数まとめて管理する仕組み
宣言方法struct タグ名 配列名[要素数];
初期化方法各要素ごとに波括弧で値を並べる
メンバへのアクセス配列名[添字].メンバ名
繰り返し処理との相性for文で全件をまとめて処理しやすい
文字列メンバの代入strcpy を使う
実用面での価値名簿、商品一覧、成績一覧などに向いている

構造体配列が使えるようになると、C言語で扱えるデータの幅が一気に広がります。
1件分の情報を構造体で表し、それを複数件並べて管理する。
この考え方は、これから先の実践的なプログラム作成でとても大切になってきます。