そこでひとまとまりのデータを集めて、新しいデータ構造(型)を定義する。 このようなデータ構造を構造体という。(配列は同種のデータをまとめたものであるが、 構造体は異種のデータをまとめることができる。)
構造体を定義する利点は関数を定義する利点と似ている。
構造体は次のような形式で宣言する。
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のように書き直すことができる。
こうしておくと学生のデータに変更があったとき(例えば構造体に修得単位数をあらわすメンバを追加する)も最小限の変更で済む。