そこでひとまとまりのデータを集めて、新しいデータ構造(型)を定義する。 このようなデータ構造を構造体という。(配列は同種のデータをまとめたものであるが、 構造体は異種のデータをまとめることができる。)
構造体を定義する利点は関数を定義する利点と似ている。
構造体は次のような形式で宣言する。
struct タグ名 {
型 メンバ名;
…
型 メンバ名;
};
例:
struct gstudent {
char name[20];
int height;
float weight;
long schols;
};
構造体に与える名前(上の例ではgstudent)をタグと呼び、 構造体の構成要素(上の例ではname, height, weight, schols)をメンバと呼ぶ。
このような構造体を格納するための変数は
struct タグ名 変数名;という形で宣言する。
例:
struct gstudent shibata;
構造体の宣言とその型の変数を同時に定義することも可能、 その場合タグ名を省略することも可能である。
struct test {
int x;
long y;
double z;
} ta tb;
struct {
int x;
long y;
double z;
} ta tb;
ただし後者はその場限りの(あまり役に立たない)構造体になってしまう。
構造体を関数の引数や戻り値に使うときは、配列と違って構造体のコピーが作成される。(つぎの2つの例を比べよ。)
addPoint.c
#include <stdio.h>
struct point {
int x;
int y;
};
struct point addPoint(struct point p1, struct point p2) {
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
int main(void) {
struct point p1 = {2, 3}, p2 = {1, 2}, p3;
p3 = addPoint(p1, p2);
printf("p1 = {%d, %d}\n", p1.x, p1.y);
printf("p3 = {%d, %d}\n", p3.x, p3.y);
return 0;
}
addPointArr.c
#include <stdio.h>
int *addPoint(int p1[], int p2[]) {
p1[0] += p2[0];
p1[1] += p2[1];
return p1;
}
int main(void) {
int p1[] = {2, 3}, p2[] = {1, 2};
int *p3;
p3 = addPoint(p1, p2);
printf("p1 = {%d, %d}\n", p1[0], p1[1]);
printf("p3 = {%d, %d}\n", p3[0], p3[1]);
return 0;
}
だから、大きな構造体を使用するときは、効率の点から構造体そのものではなく、
構造体へのポインタを渡すことがよく行われる
(List 12-5)。
typedef 型 新しい型名;例:
struct gstudent {
char name[20];
int height;
float weight;
long schols;
};
typedef struct gstudent student;
以降はstruct gstudentの代わりにstudentと書くことができる。
さらに一気に、
typedef struct {
char name[20];
int height;
float weight;
long schols;
} student;
とすることも可能である。
配列と異なり、構造体の代入は可能である。 (コピーされるので、一般的にはサイズの大きな構造体は代入を避ける。)
int p1[] = {1, 2}, p2[2];
p2 = p1; /* NG */
struct point p1 = {1, 2}, p2;
p2 = p1; /* OK */
関数内で宣言した構造体を戻り値にしてもよい(List 12-7)。
この章の冒頭のプログラム (List 12-2)は構造体を使って、 List 12-8のように書き直すことができる。
こうしておくと学生のデータに変更があったとき(例えば構造体に修得単位数をあらわすメンバを追加する)も最小限の変更で済む。