「構造体」

構造体

ある程度以上の規模のプログラムを書く際、 関連するデータをひとまとめにして扱うとデータの管理が楽になる場合が ほとんどである。
構造体は、関連するデータをひとまとめにして扱うデータ構造である。

以下の例では、人物に関するデータをひとまとめにして構造体として宣言して いる。 (この例では、人物の氏名(最大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 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に設定される)

struct complex_number a = { 3.0, 4.0 };
struct complex_number b = { 1.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宣言

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"として使用することも可能となる)


文責:石川