Lesson 9
構造体
目次
Lesson 9
Chapter 1
構造体
本Chapterでは構造体(こうぞうたい)と呼ばれる型について学習します。現在までの学習で扱った型は、int型、float型、char型といったように、C言語によってあらかじめ決められた形式を示し、ユーザ側で定義した変数に、その型に対応した値しか格納できませんでした。それに対して構造体は、今までとまったく異なる概念を持つ型です。構造体は「いくつかの型を組み合わせた新しい型」になります。構造体がどのようなものであるかイメージすることが難しいと思いますので、プログラムと合わせて、より具体的な例をあげて解説します。
あるクラスの生徒30人に対してそれぞれ「出席番号(整数)」「名前(文字列)」「身長(小数)」を、プログラム内で格納しておきたい場合には、どのような変数を作成するでしょうか。
int no[30];
char name[30];
float height[30];
今まで学習した知識で考えると、上記のような配列を定義し、1番目の生徒の情報はno[0]、name[0]、height[0]に格納する、という考え方になるでしょう。この方法は間違いではなく、配列を使った正しい方法の1つです。この配列によるデータのまとめ方は、生徒を基準に値を管理するというよりは、出席番号、名前、体重を基準として、それぞれの配列を用意してデータの管理を行っています。
配列を利用する場合には、出席番号、身長、体重のようにそれぞれ異なる型の値を保存することができず、配列を別々に用意する必要があります。そこで、構造体と呼ばれる新しい型を活用します。構造体を利用すれば、上記の図ように異なる型の値を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 構造体名 配列名[要素数];
構造体を返す関数の定義
別名 配列名[要素数];
構造体型の配列を初期化するには、次のように書きます。配列の初期化には{}を使いますが、構造体の配列を初期化する際には、一般に[]を入れ子にして使います。以下では内側の{}が、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
