C 言語には複数の変数を同時に扱うために構造体が用意されている。
複素数を扱うプログラムを考えよう。 C 言語には複素数のための型は用意されていないので、 複素数を実部と虚部に分けて二つの変数で表すことになる。 例えば、一つの複素数 a に対して、その実部を a1、虚部を a2 とする等である。 しかしこの様な記述はプログラムを煩雑にするであろう。 そこで二つ以上の変数を同時に扱う仕組みが必要となる。
struct compnum { float re; float im; };は二つの float をもつ新しい型 struct compnum を定義する。 このようなものを構造体という。 実際のプログラムを見てみよう。
#include <stdio.h> struct compnum { float re; float im; }; struct compnum compadd(struct compnum a, struct compnum b) { struct compnum c; c.re = a.re + b.re; c.im = a.im + b.im; return c; } int main(int argc, char *argv[]) { struct compnum x, y = {2.0, -1.5}, z; x.re = 1.0; x.im = 2.5; z = compadd(x, y); printf("x = %f + %f i\n", x.re, x.im); printf("y = %f + %f i\n", y.re, y.im); printf("x + y = %f + %f i\n", z.re, z.im); return 0; }定義された構造体は通常の変数のように扱うことが出来る。 構造体に含まれる変数は構造体のメンバーと呼ばれ、 x.re のようにして通常の変数のようにアクセスできる。 また上の例の y のように構造体のメンバーは宣言時に初期化できる。 構造体は普通の変数のように関数の引数や戻り値にも指定できる。
課題 07.01
compnum の乗法を行う関数を定義書け。
構造体の変数の型は違っていても構わない。 例えば、これまでにいくつかの例でベクトルを扱ってきた。 関数にベクトルを渡す際にはベクトルの長さも同時に与えた。そこで
struct vector { float *p; int dim; };のような構造体を考えれば、ベクトルは自分でその長さに関する情報ももつことになる。 ただしこの場合には p には malloc などでメモリを割り当ててある必要がある。
構造体はメンバーに構造体をもつことも出来る。 例えば複素数を成分とするベクトルは
struct cvector { struct compnum *p; int dim; };などとなるであろう。
課題 07.02
上記の cvector の加法を行う関数を定義し、その動きを確認せよ。
学生の成績を管理するためのプログラムを書くとしよう。 学生を一つの配列で管理し、ある科目の成績を一つの配列で管理し、 などとするとデータの整合性に問題が生じやすく混乱の元となる。 この場合は適当な構造体を用意し、 一人の学生に対して一つの構造体を定義するのが良いであろう。 この場合には一つの構造体がたくさんのデータをもつことになる。 (実際にはこの様な作業にはデータベースを用いるべきであり、 すべてを C 言語で書こうとするのは効率が悪い。) 関数には引数として構造体を渡すことができるが、 このように構造体が大きい時にはそのコピーを作るため効率が悪くなる。 そこでアドレスを渡すことにすればコピーを作らない分だけ効率が上がる。
構造体のポインタは通常の変数の場合と同じように扱うことが出来る。
struct compnum *p;などである。
struct compnum x; struct compnum *p; p = &x;とすれば p は x のアドレスを値にもつ。*p は x と等しい。 p からメンバーの値を参照するには (*p).re などとすればよいが、 構造体のポインタからそのメンバーを参照することは多いので、 特別な記号が用意されている。 p->re は (*p).re と同じ意味をもつ。
課題 07.03
上の例の compnum の加法 compadd をポインタを使う形に書き換えよ。
この際、戻り値は void とし compadd(&x, &y, &z) のように呼び出すようにせよ。
C 言語では変数に別名を付けることが出来る。 例えば同じ整数でも違う意味に用いている場合には違う名前を付けていると、 宣言を見ただけでその違いがはっきりとするであろう。 また、例えばあるものの個数を表すために int a として宣言している場合に、 後でより大きな値を扱うことが出来る long a と変更したい場合もある。 このとき、例えば int に別名 count が与えられていて count a としていたならば、 count は long の別名であると定義し直せば良い。 このように必要に応じて変数には別名を付けると便利であるが、 あまりやりすぎるとプログラムの可読性が下がってしまう。
特に構造体を示す型は struct compnum のように長くなりがちなので、 適当に別名を付けると便利である。
typedef int count;は int に別名 count を与える。 以後 count a のように通常の変数と同じように扱うことが出来る。
typedef struct compnum { float re; float im; } cnum;は構造体を定義すると同時にこの構造体に別名 cnum を与える。 別名を与えているだけなので、使い方は変わらない。
構造体はそのメンバーに自分と同じ構造体を含むことは出来ない。 それは無限の呼び出しを意味するからである。 しかしメンバーに同じ構造体のポインタを含むことは可能である。
struct s { int a; struct s *p; };また相互参照も可能である。
struct s { int a; struct t *p; }; struct t { int a; struct s *p; };これらについては深く説明はしないがデータのリスト構造やツリー構造などを考えるときに頻繁に用いられる。