Lesson 9

構造体

Lesson 9 Chapter 1
構造体

本Chapterでは構造体(こうぞうたい)と呼ばれる型について学習します。現在までの学習で扱った型は、int型、float型、char型といったように、C言語によってあらかじめ決められた形式を示し、ユーザ側で定義した変数に、その型に対応した値しか格納できませんでした。それに対して構造体は、今までとまったく異なる概念を持つ型です。構造体は「いくつかの型を組み合わせた新しい型」になります。構造体がどのようなものであるかイメージすることが難しいと思いますので、プログラムと合わせて、より具体的な例をあげて解説します。

あるクラスの生徒30人に対してそれぞれ「出席番号(整数)」「名前(文字列)」「身長(小数)」を、プログラム内で格納しておきたい場合には、どのような変数を作成するでしょうか。

int no[30];
char name[30];
float height[30];

10_3.png

今まで学習した知識で考えると、上記のような配列を定義し、1番目の生徒の情報はno[0]、name[0]、height[0]に格納する、という考え方になるでしょう。この方法は間違いではなく、配列を使った正しい方法の1つです。この配列によるデータのまとめ方は、生徒を基準に値を管理するというよりは、出席番号、名前、体重を基準として、それぞれの配列を用意してデータの管理を行っています。

10_4.png

配列を利用する場合には、出席番号、身長、体重のようにそれぞれ異なる型の値を保存することができず、配列を別々に用意する必要があります。そこで、構造体と呼ばれる新しい型を活用します。構造体を利用すれば、上記の図ように異なる型の値を1つの変数の中に格納しておくことが可能になります。このように、構造体の利点は、データのまとまりを1つのデータかのように扱える点です。次は、構造体の具体的な使用方法について解説していきます。

structで構造体を宣言する

構造体は次のように宣言します。struct (ストラクト)は、structure (ストラクチャー、構造体)の略です。

構造体の宣言
struct 構造体名;

構造体の定義は以下になります。

構造体の定義
struct 構造体名 {
    型 変数名;
    …

};

構造体名は、構造体タグと呼ばれることもあります。構造体名のつけ方は、変数名のつけ方と同じです。{}(波括弧)の中には、変数の宣言を書きます。これらの変数はメンバと呼ばれます。型はメンバごとに異なっても、同じでも構いません。また、メンバとして配列を宣言することもできます。メンバ名(変数名や配列名)のつけ方は、通常の変数名のつけ方と同じです。型が同じメンバは、通常の変数と同様に,で区切ればまとめて宣言できます。構造体の定義は、1行で書いても複数行に分けて書いても構いません。関数の定義とは違い、末尾に「;」をつけることに注意してください。

「.」演算子

宣言または初期化した構造体型の変数に対して、次のように書くと、メンバの値を読み書きできます。.(ドット)演算子は、構造体のメンバにアクセスするための演算子です。

構造体のメンバを読み書き
変数名.メンバ名;

typedef宣言で、構造体に別名を与える

typedef宣言というのは、型に別名をつける機能です。typedef宣言を使って、構造体型に別名をつけると「struct 構造体名」のようにstructを書かなくても済むようになります。基本のtypedef宣言は次のように書きます。

型に別名をつける
typedef 型 別名;

構造体の定義と同時に別名をつける場合には、次のように書きます。別名をつければ、多くの場合は構造体名を使わなくて済むので、以下では構造体名を省略しています。

構造体に別名をつける
typedef sttruct {
    型 変数名;
    …

} 別名;

typedef宣言でつける別名は、変数名や関数名と同じく識別子の一種です。別名のつけ方は、変数名のように英小文字を使う方法と、定数名のように英大文字を使う方法の、両方を見かけます。typedef宣言で構造体型に別名をつけたら、次のように変数の宣言や初期化ができます。「struct 構造体名」と書くよりも、短く書けることが利点です。

型に別名をつける
別名 変数名;
型を使って構造体型の変数を初期化
別名 変数名 = {値, …};

構造体を用いたプログラムの例

typedef宣言を使って構造体型にFRUIT(フルーツ)という別名をつけた上で、この別名を使って構造体型の変数を初期化し、メンバの値を出力してください。

struct1.c
#include <stdio.h>

// 構造体の定義とtypedef宣言
typedef struct {
  char name[100];
  int price;
  double weight;

} FRUIT;

// main関数の定義
int main(void) {

  // 別名を使って構造体型の変数を初期化
  FRUIT a = {"orange", 200, 80.7};
  
  // メンバの値を表示
  printf("%s, %d %f g\n" , a.name, a.price, a.weight);

  return 0;
}
実行結果
struct1.exe
orange : 200 yen 80.7 g

関数の返却値としての構造体

構造体は関数と簡単に組み合わせて使えます。構造体を関数の戻り値として返すことが可能です。構造体を使うことの利点は、関数との間で複数の値をまとめて受け渡しできることです。構造体を関数の戻り値として返すには、関数を定義する際に、戻り値型を構造体型にします。typedef宣言でつけた別名も使えます。

構造体を返す関数の定義①
struct 構造体名 関数名(…) {…}
構造体を返す関数の定義②
別名 関数名(…) {…}

構造体の戻り値を返す関数を追加してみましょう。キーボードから入力した名前・価格・重量をFRUIT型の構造体に格納し、戻り値として返すinput関数を定義し、main関数から呼び出して、戻り値をprint関数で出力するプログラムを書いてください。input関数では、scanf関数を使って、構造体のメンバに値を格納します。以前学んだように、scanf関数の引数には変数のアドレスを渡す必要があります。メンバのnameはアドレス(配列の先頭アドレス)なのでそのまま渡せます。一方でpriceとweightは、次のようにアドレスを取得します。

メンバのアドレスを取得
&変数.メンバ名
struct2.c
#include <stdio.h>

// 構造体の定義とtypedef宣言
typedef struct {
  char name[100];
  int price;
  double weight;

} FRUIT;

// input関数
FRUIT input(void) {

  FRUIT a;
  
  printf("name price weight: ");
  scanf("%s%d%lf", a.name, &a.price, &f.weght);
  
  return a;
}

// print関数
void print(FRUIT a) {
  printf("%s, %d yen %f g\n", a.name, a.price, a.weight);
}

// main関数
int main(void) {

  print(input());
  
  return 0;
}
実行結果
struct2.exe
name price weight: apple 150 50.5
apple, 150 yen 50.5 g

構造体を要素とする配列

構造体を配列にすると、さらに効果的に活用できます。例えば商品の情報を扱うプログラムにおいて、商品の名前・価格・重量を構造体にまとめた上で、配列を使ってこの構造体を並べれば、多数の商品情報を管理できます。こういった情報はデータベースを使って表現することがよくありますが、構造体とファイル入出力を使って表現することも可能です。
構造体を配列にするには、構造体型の配列を宣言します。次のように、配列の宣言における要素の型を、構造体型にします。typedef言でつけた別名も使えます。

構造体を返す関数の定義
struct 構造体名 配列名[要素数];
構造体を返す関数の定義
別名 配列名[要素数];

10_2.png

構造体型の配列を初期化するには、次のように書きます。配列の初期化には{}を使いますが、構造体の配列を初期化する際には、一般に[]を入れ子にして使います。以下では内側の{}が、1個の構造体を初期化します。多次元配列を初期化する際の書き方に似ています。

構造体型の配列を初期化①
struct 構造体名 配列名[] = {{値, …},{値, …} …};
構造体型の配列を初期化②
別名 配列名[] = {{値, …},{値, …} …};

構造体型の配列を使ってみましょう。これまでのプログラムと同様に、FRUIT型を使います。構造体型の配列を初期化し、次のような商品一覧を出力するプログラムを書いてください。

struct3.c
#include <stdio.h>

// 構造体の定義とtypedef宣言
typedef struct {
  char name[100];
  int price;
  double weight;

} FRUIT;

// main関数
int main(void) {

  // 構造体型の配列fruitを初期化
  FRUIT fruit[]={{"apple", 150, 50.5},
               {"orange", 200, 80.7},
               {"banana", 120, 21.0}};
    
  
  // 配列の要素数を計算
  int size = sizeof(fruit) / sizeof(fruit[0]); 
  
  puts("name price weight");
  
  // for文で一覧を出力
  for (int i = 0; i < size; i++) {
    printf("%-6s %5d %6.1f\n", fruit[i].name, fruit[i].price, fruit[i].weight);
  }
  
  return 0;
}
実行結果
struct3.exe

name     price weight
apple    150   50.5
orange   200   80.7
banana   120   21.0