ある程度以上の規模のプログラムを書く際、関連するデータをひとまとめにして扱うとデータの管理が楽になる場合がほとんどである。
構造体は、関連するデータをひとまとめにして扱うためのデータ構造である。
以下の例では、人物に関するデータをひとまとめにして構造体として宣言している。(この例では、人物の氏名(最大39文字まで)、年齢、身長、体重を扱う)
/* 人物データ */
struct person {
char name[40]; /* 氏名 */
int age; /* 年齢 */
float height; /* 身長 */
float weight; /* 体重 */
};
personを構造体のタグと呼び、{ }の中のname,age,height,weightを構造体のメンバーと呼ぶ。
イメージとして、箱の中に氏名・年齢・身長・体重が入ったものに personという名前をつけて、一つのデータ型として扱えるようにしたものと考えると分かりやすい。
構造体を宣言した後、以下のように宣言することで、構造体person型の変数personA が使えるようになる。
メンバーを参照する際は、.(ドット)演算子を使う。
/* 構造体person型の変数 personAの宣言 */
struct person personA;
/* ドット演算子でメンバーを参照 */
strcpy(personA.name, "Mr.A");
personA.age = 24;
personA.height = 170.0;
personA.weight = 65.5;
printf("name=%s age=%d height=%f weight=%f\n",
personA.name, personA.age, personA.height, personA.weight);
また、以下の例では、2次元の点を表現する構造体である。メンバーとしてx, yを持ち、構造体タグはpoint2dである。
/* 2次元上の点 */
struct point2d {
int x;
int y;
};
struct point2d a, b; /* 構造体 point2d型の変数 a, bの宣言 */
a.x = 0; a.y = 0; /* ドット演算子でメンバーを参照 */
b.x = 640; b.y = 400;
printf("a: x=%d y=%d\n", a.x, a.y);
printf("b: x=%d y=%d\n", b.x, b.y);
/* 例えば、関連するデータをひとまとめにして関数の引数として渡せる */
funcA(a, b);
構造体を用いる利点は、関連するデータをひとまとめに管理することで、データの構造が分かりやすくなることである。(また、他人が書いたコードも理解しやすくなる)
complex-test.cでは、複素数(complex number)を表現するデータ型が構造体で宣言されています。プログラムをコンパイル・実行し、構造体のメンバ変数の使い方を確認してください。
#include <stdio.h>
struct complex_number { /* 複素数を表現する構造体 */
double real; /* 実数部 */
double imag; /* 虚数部 */
};
/* 複素数の加算 */
struct complex_number complex_number_add(struct complex_number a, struct complex_number b)
{
struct complex_number result;
result.real = a.real + b.real;
result.imag = a.imag + b.imag;
return result;
}
/* 複素数の減算 */
struct complex_number complex_number_sub(struct complex_number a, struct complex_number b)
{
struct complex_number result;
result.real = a.real - b.real;
result.imag = a.imag - b.imag;
return result;
}
/* 複素数の乗算 */
struct complex_number complex_number_mul(struct complex_number a, struct complex_number b)
{
struct complex_number result;
result.real = a.real * b.real - a.imag * b.imag;
result.imag = a.real * b.imag + a.imag * b.real;
return result;
}
/* 複素数の除算 */
struct complex_number complex_number_div(struct complex_number a, struct complex_number b)
{
struct complex_number result = { 0.0, 0.0 };
double d;
d = b.real * b.real + b.imag * b.imag;
if (d == 0.0) {
printf("divided by zero\n");
return result;
}
result.real = (a.real * b.real + a.imag * b.imag)/d;
result.imag = (- a.real * b.imag + a.imag * b.real)/d;
return result;
}
/* 複素数の表示 */
void complex_number_print(struct complex_number a)
{
if (a.imag < 0)
printf("%f-%fi", a.real, a.imag);
else
printf("%f+%fi", a.real, a.imag);
}
void calc_test(struct complex_number a, struct complex_number b, char optype)
{
struct complex_number c;
switch (optype) {
case '+':
c = complex_number_add(a, b);
break;
case '-':
c = complex_number_sub(a, b);
break;
case '*':
c = complex_number_mul(a, b);
break;
case '/':
c = complex_number_div(a, b);
break;
default:
printf("unknown operation type %c\n", optype);
return;
break;
}
complex_number_print(a);
printf(" %c ", optype);
complex_number_print(b);
printf(" = ");
complex_number_print(c);
printf("\n");
}
int main(void)
{
struct complex_number a, b; /* 構造体complex_number型の変数の宣言 */
a.real = 3.0; a.imag = 4.0; /* 変数aの値の設定 */
b.real = 1.0; b.imag = 2.0; /* 変数bの値の設定 */
/* 2つの複素数の四則演算結果を表示 */
calc_test(a, b, '+');
calc_test(a, b, '-');
calc_test(a, b, '*');
calc_test(a, b, '/');
return 0;
}
また、以下のようにすることで、構造体の変数の宣言と同時に値の初期化を行なうこともできます。(宣言時に初期化をしない場合は全て0に設定される)
c,dの四則演算結果を表示してみましょう。
struct complex_number c = { 4.0, 1.0 };
struct complex_number d = { 3.0, 2.0 };
構造体変数の各メンバーを参照する場合は、ドット演算子"."を使えばよいが、構造体をポインタを介して使う場合に、メンバーを参照するには以下のようにして、ポインタ変数を間接演算子"*"を用いて構造体を参照する部分を括弧で括り、それに続いてドット演算子"."を用いて構造体のメンバーを参照する必要がある。
struct complex_number *pa; /* 構造体のポインタ変数 */
...
(*pa).real = 1.0; /* 括弧で囲む */
(*pa).imag = 2.0; /* 括弧で囲む */
以下のように書いた場合、演算子の優先順位の都合で、先にドット演算子が評価されるためエラーとなる。(式の意味としては、"*(pa.real)"と書いた場合と等価になるため、"pa.real"の部分でエラーとなる)
*pa.real = 1.0; /* エラー */
*pa.imag = 2.0; /* エラー */
ただしこの場合は、->(アロー)演算子を用いることで簡潔に表現できる。
pa->real = 1.0; /* ->演算子を使用 */
pa->imag = 2.0; /* ->演算子を使用 */
typedef宣言は型に対する別名を宣言する。
以下の例では、unsigned int型をsize_tという別名で使えるようになり、以後、size_t型のfsizeを宣言することで、unsigned int(符号なし整数)型の変数fsizeが使えるようになる。
typedef unsigned int size_t;
...
size_t fsize;
前述の複素数を表現する構造体complex_number型を使う場合に、変数宣言などを行なう毎に"struct complex_number"と書いていたものも、このtypedef宣言を行なうことで、以下のように簡単化できる。
typedef struct {
double real;
double imag;
} complex_number;
...
complex_number a, b;
この場合、構造体タグをつけてもつけなくても構わない。
(タグをつけた場合は、これまで通り "struct complex_number"として使用することも可能となる)