構造体を使うと、いくつかの異なったタイプのデータをひとかたまりのものとして扱えます。 ここでは構造体についての基本的なことを学びます。 感覚的には「既存の型の組み合わせを使って、自分好みの新しい構造を持った型を作る。」というような感じで捉えておけば良いと思います。
次回の講義では「ファイル構造体」というものを習います。 また、後期の終わりに C 言語を拡張した C++ 言語を少しやります。 そこではここで学習する構造体を拡張したクラスというものが出てきます。 更に応用プログラミングIIの方の JAVA 言語でもクラスというものが出てきます。 クラスに関する理解の準備のためにも構造体をしっかり身につけて下さい。
いくつかの異なったタイプのデータをひとかたまりにして構造体(structure)を構成します。 例えば「学生証番号(整数データ)」と「名前(文字列データ)」と「英語の点数(整数データ)」をひとかたまりにした構造体を seisekiTag という名前で定義するときには次のように
struct seisekiTag {
int ID;
char name[64];
int english;
};
と書きます。(一番最後のセミコロン、忘れがちになるので注意)
int ID;
の部分は「学生証番号に対応するメンバ」です。整数型でメンバ名を「ID」としました。char name[64];
の部分は「名前に対応するメンバ」です。文字列型でメンバ名を「name」としました。int english;
の部分は「英語の点数に対応するメンバ」です。整数型でメンバ名を「english」としました。 struct 構造体タグ {
型 メンバ1;
型 メンバ2;
・・・・
};
定義した構造体タグを使って構造体変数を次のように宣言します。
struct seisekiTag seiseki1, seiseki2, seiseki3;
これで seisekiTag で定義された構造を持った変数 seiseki1 と seiseki2 と seiseki3 を宣言(メモリのどこかに変数を格納する領域を確保)したことになります。
seisekiTag で定義された構造を持った変数のことを「struct seisekiTag 型変数」と言うこともあります。
一般的には次のように宣言します。
struct 構造体タグ 構造体変数1, 構造体変数2, ・・・・;
また、次のようにすれば構造体の定義と変数の宣言を同時に行うこともできます。
struct seisekiTag {
int ID;
char name[64];
int english;
} seiseki1, seiseki2, seiseki3;
この場合は構造体タグ (seisekiTag の部分) を省略しても構いません。
構造体のメンバにアクセスする際にはドット演算子「.
」を使います。
構造体変数名.メンバ名
で構造体変数の各メンバにアクセス(値を格納したり、格納されてる値を取り出したり)できます。
例えば構造体変数 struct1 のメンバの ID に数値 1 を代入するときは次のように書きます。
seiseki1.ID = 1;
struct1 のメンバ name に文字列 "金沢太郎" を代入するときは標準ライブラリ関数の strcpy
を使って
strcpy(seiseki1.name, "金沢太郎");
とします。
seiseki.name = "金沢太郎";
とすることは出来ません。
(name
を文字列ではなくポインタで宣言してれば出来ますが、ややこしくなりそうなので説明は省略します。)
構造体変数は宣言と同時に初期化することが出来ます。 例えば
struct seisekiTag seiseki1 = {1, "金沢太郎", 80};
とすると seiseki1 の1番目のメンバ ID に 1 を、2番目のメンバ name に "金沢太郎" を、3番目メンバ english に 80 を初期値として与えることになります。
同じ定義の構造体同士なら次の式で全てのメンバを代入(コピー)することが出来ます。
構造体変数1 = 構造体変数2;
上の式で構造体変数2の各メンバの全ての値を構造体変数1に代入することになります。
例えば
seiseki1 = seiseki2;
とするのは
seiseki1.ID = seiseki2.ID;
strcpy(seiseki1.name, seiseki2.name);
seiseki1.english = seiseki2.english;
と書くのと同じことになります。
構造体を使ったサンプルプログラムです。
1 #include <stdio.h>
2 #include <string.h>
3 void main() {
4 struct seisekiTag {
5 int ID;
6 char name[64];
7 int english;
8 };
9 struct seisekiTag seiseki1, seiseki2;
10 seiseki1.ID = 1;
11 strcpy(seiseki1.name, "金沢太郎");
12 seiseki1.english = 80;
13 printf("成績1\n");
14 printf("ID=%d\n", seiseki1.ID);
15 printf("名前=%s\n", seiseki1.name);
16 printf("英語点数=%d\n\n", seiseki1.english);
17 seiseki2 = seiseki1;
18 printf("成績2\n");
19 printf("ID=%d\n", seiseki2.ID);
20 printf("名前=%s\n", seiseki2.name);
21 printf("英語点数=%d\n", seiseki2.english);
22 }
成績1 ID=1 名前=金沢太郎 英語点数=80 成績2 ID=1 名前=金沢太郎 英語点数=80
構造体変数の配列を宣言したりもできます。
例えば
struct seisekiTag seiseki[3];
と宣言します。
各要素のメンバにはドット演算子を使ってアクセスできます。 例えば
seiseki[2].ID
とするとstruct seisekiTag型の配列 seiseki の3番目の要素 seiseki[2] のメンバ名 ID にアクセスできます。
構造体へのポインタの宣言は
struct seisekiTag *seiseki_ptr;
と宣言します。
ポインタ変数 seiseki_ptr
には struct seisekiTag 型の値を格納するメモリのアドレスを代入します。
間接参照演算子を使ってアドレスの先を参照します。
本文で *seiseki_ptr
と書くことで参照先の構造体を操作することが出来ます。
例えば
(*seiseki_ptr).ID
で参照先のメンバ名IDにアクセスできます。
また、(*seiseki_ptr).ID
の命令と全く同じ命令をアロー演算子「->
」を使って
seiseki_ptr->ID
と書くこともできます。
構造体へのポインタの話まで行くとややこしくなるのでこれくらいにしておきますが、単に構造体の考え方とポインタの考え方を組み合わせただけなので、じっくり考えれば理解できます。
これまでの例では構造体を宣言するときに struct studentTag
と少し長たらしい書き方になってました。
例題では main 関数のみで数回しか宣言しないのでそれでも良かったのですが、複数の関数で同じ定義の構造体を使うときに何度も宣言する場合は面倒臭くなってきます。
ここでは typedef 指定子を利用して構造体に新しい型名(別名)をつけて扱う方法を説明します。
typedef は読んで字のごとく型名(type name)を定義(define)するという意味です。 次のように使います。
typedef 別名をつけたい型 別名;
先の例の学生の英語の成績の構造体に別名 SEISEKI をつけたい場合は次のようにします。
struct seisekiTag {
int ID;
char name[64];
int english;
};
typedef struct seisekiTag SEISEKI;
最後の typedef struct seisekiTag SEISEKI;
で struct seisekiTag
という型の別名 SEISEKI
という型名を定義してます。
(別名は予約語でなければ大文字小文字どちらでも使えますが、構造体の別名は慣習的に大文字を使っていることが多いです。)typedef struct {
int ID;
char name[64];
int english;
} SEISEKI;
これで ID, namem, english
の3つのメンバを持つ構造体に別名 SEISEKI
をつけたことになります。
サンプルプログラムです。
1 #include <stdio.h>
2 #include <string.h>
3 typedef struct {
4 int ID;
5 char name[64];
6 int english;
7 } SEISEKI;
8 void main() {
9 SEISEKI seiseki1, seiseki2;
10 seiseki1.ID = 1;
11 strcpy(seiseki1.name, "金沢太郎");
12 seiseki1.english = 80;
13 printf("成績1\n");
14 printf("ID=%d\n", seiseki1.ID);
15 printf("名前=%s\n", seiseki1.name);
16 printf("英語点数=%d\n\n", seiseki1.english);
17 seiseki2 = seiseki1;
18 printf("成績2\n");
19 printf("ID=%d\n", seiseki2.ID);
20 printf("名前=%s\n", seiseki2.name);
21 printf("英語点数=%d\n", seiseki2.english);
22 }
成績1 ID=1 名前=金沢太郎 英語点数=80 成績2 ID=1 名前=金沢太郎 英語点数=80
構造体を構造体のメンバにすることができます。 構造体の中に構造体が入れ子のように入っている状態です。 入れ子の事をネスト(nest)とも言います。
以下の例は英語、国語、数学の点数で構造体 EXAM
を定義し、
それを 構造体 SEISEKI
のメンバに組み込んでます。
構造体の中の構造体のメンバにはドット演算子を2回使ってアクセスします。
1 #include <stdio.h>
2 typedef struct {
3 int english;
4 int japanese;
5 int math;
6 } EXAM;
7 typedef struct {
8 int ID;
9 char name[64];
10 EXAM siken;
11 } SEISEKI;
12 void main() {
13 SEISEKI seiseki1 = {1, "金沢太郎", {80, 70, 75}};
14 SEISEKI seiseki2 = {2, "石川次郎", {60, 50, 45}};
15 printf("成績1\n");
16 printf("ID=%d\n", seiseki1.ID);
17 printf("名前=%s\n", seiseki1.name);
18 printf("英語点数=%d\n", seiseki1.siken.english);
19 printf("国語点数=%d\n", seiseki1.siken.japanese);
20 printf("数学点数=%d\n", seiseki1.siken.math);
21 printf("\n");
22 printf("成績2\n");
23 printf("ID=%d\n", seiseki2.ID);
24 printf("名前=%s\n", seiseki2.name);
25 printf("英語点数=%d\n", seiseki2.siken.english);
26 printf("国語点数=%d\n", seiseki2.siken.japanese);
27 printf("数学点数=%d\n", seiseki2.siken.math);
28 }
成績1 ID=1 名前=金沢太郎 英語点数=80 国語点数=70 数学点数=75 成績2 ID=2 名前=石川次郎 英語点数=60 国語点数=50 数学点数=45