構造体


構造体とは

構造体は、複数のデータをまとめて扱うためのものである。 配列も複数のデータをまとめて扱うが、配列との違いは、 という点にある。

次のプログラムは、複素数 x を入力し、それに対して x2+xを 計算するものである。

#include <stdio.h>

void complex_add(double are, double aim, double bre, double bim, double *cre, double *cim)
{
    *cre = are + bre;
    *cim = aim + bim;
}

void complex_mul(double are, double aim, double bre, double bim, double *cre, double *cim)
{
    *cre = are * bre - aim * bim;
    *cim = are * bim + aim * bre;
}

void complex_print(double are, double aim)
{
    printf("(%f)+(%f)i", are, aim);
}

int main(void)
{
    double xre, xim, yre, yim, tmpre, tmpim;

    scanf("%lf", &xre);
    scanf("%lf", &xim);

    complex_mul(xre, xim, xre, xim, &tmpre, &tmpim);
    complex_add(tmpre, tmpim, xre, xim, &yre, &yim);

    complex_print(yre, yim);
    printf("\n");

    return 0;
}
complex1.c

複素数なので、実部 (real part) と虚部 (imaginary part) が必ずペアで 現れていることが分かる。しかし、変数を2つ用意し別々に宣言や受渡しを 行っており、見通しが悪い。 構造体を使うと、両者が一組であるということをより分かりやすく表現する ことが出来る。

#include <stdio.h>

struct complex {
    double re;
    double im;
};

void complex_add(struct complex a, struct complex b, struct complex *c)
{
    (*c).re = a.re + b.re;
    (*c).im = a.im + b.im;
}

void complex_mul(struct complex a, struct complex b, struct complex *c)
{
    (*c).re = a.re * b.re - a.im * b.im;
    (*c).im = a.re * b.im + a.im * b.re;
}

void complex_print(struct complex a)
{
    printf("(%f)+(%f)i", a.re, a.im);
}

int main(void)
{
    struct complex x, y, tmp;

    scanf("%lf", &(x.re));
    scanf("%lf", &(x.im));

    complex_mul(x, x, &tmp);
    complex_add(tmp, x, &y);

    complex_print(y);
    printf("\n");

    return 0;
}
complex2.c

また、構造体にすれば「一つの変数」なので戻り値を直接返せるので、 次のようにより簡潔に書くことも出来る。

#include <stdio.h>

struct complex {
    double re;
    double im;
};

struct complex complex_add(struct complex a, struct complex b)
{
    struct complex c;

    c.re = a.re + b.re;
    c.im = a.im + b.im;

    return c;
}

struct complex complex_mul(struct complex a, struct complex b)
{
    struct complex c;

    c.re = a.re * b.re - a.im * b.im;
    c.im = a.re * b.im + a.im * b.re;

    return c;
}

void complex_print(struct complex a)
{
    printf("(%f)+(%f)i", a.re, a.im);
}

int main(void)
{
    struct complex x, y, tmp;

    scanf("%lf", &(x.re));
    scanf("%lf", &(x.im));

    tmp = complex_mul(x, x);
    y = complex_add(tmp, x);

    complex_print(y);
    printf("\n");

    return 0;
}
complex3.c

以下、これをサンプルとして、構造体の使い方を説明する。

構造体の定義

struct タグ名 {
    型名 メンバ名;
    型名 メンバ名;
    ...
};
のように書く。例えば、
struct complex {
    double re;
    double im;
};
のように書ける。異なる型が混在していてもよい。例えば、
struct person {
    char name[100];
    int age;
    double height;
    double weight;
};
のように書くことが出来る。

構造体の定義は、「タグ名」の構造体がどのような構造を持つかを 定義したに過ぎず、これだけでは実際のメモリ領域は割り当てられていない。

構造体を使う

struct タグ名 変数名;
のように宣言する。例えば、
struct complex x;
と書く。普通の変数と同様に、宣言と同時に初期化することも出来る。例えば、
struct complex x = {1.0, 2.0};
中身を取り出すときは、
変数名.メンバ名
のように書く。例えば、x.rex.imなど。

構造体とポインタ

構造体は一般に大きいサイズになることが多く、関数間で構造体を 受け渡すときはポインタ経由にすることが多い。ポインタを使って 参照渡しをすれば、どんなに大きなデータでも実際に受け渡されるのは ポインタの大きさ(例えば4byte)で済む。

構造体をポインタで渡すと、

    (*x).re
のような記述がどうしても多くなるが、これに関しては、
    x->re
のような略記法がある。例えば上の例のcomplex_add関数は、
void complex_add(struct complex a, struct complex b, struct complex *c)
{
    c->re = a.re + b.re;
    c->im = a.im + b.im;
}
と書いてもよい。

いわゆるsyntax sugar, syntactic sugar (糖衣構文)の一種。

構造体とtypedef

C言語には、typedefという、型の名前に別名をつける機能がある。
typedef 型名 型の別名;
という使い方。例えば、
typedef unsigned int uint;

main()
{
    unsigned int a;
    uint b;
}
のように使う。uintという名前が、unsigned intの別名として使えるようになる。

これを利用して、構造体に別名を付けて使うことが多いので、例として挙げておく。

#include <stdio.h>

typedef struct {
    double re;
    double im;
} complex;

complex complex_add(complex a, complex b)
{
    complex c;

    c.re = a.re + b.re;
    c.im = a.im + b.im;

    return c;
}

complex complex_mul(complex a, complex b)
{
    complex c;

    c.re = a.re * b.re - a.im * b.im;
    c.im = a.re * b.im + a.im * b.re;

    return c;
}

void complex_print(complex a)
{
    printf("(%f)+(%f)i", a.re, a.im);
}

int main(void)
{
    complex x, y, tmp;

    scanf("%lf", &(x.re));
    scanf("%lf", &(x.im));

    tmp = complex_mul(x, x);
    y = complex_add(tmp, x);

    complex_print(y);
    printf("\n");

    return 0;
}
complex4.c

つまり、構造体にcomplexという別名を付けることによって、使うときに いちいちstructと書かなくて済むようになる。

先頭の宣言部は本来は

typedef struct complex {
    double re;
    double im;
} complex;
だが、この場合別名が付けられたことによって構造体を特定するための タグが不要になるため、タグ名を省略している。
構造体