11.構造体

11-3. 構造体の宣言をもっと便利に

◇構造体変数の宣言をもっと便利に

構造体を宣言するとき、

struct st_samp 識別子;

というふうに、常にstructをつけなければならないのが面倒です。
ここで typedef を用いて次のように構造体を定義すると後で非常に楽になります。

#include<stdio.h>

typedef struct st_samp{  /* この行と */
        char *title;
        int  no;
        char *date;
}S_samp;                 /* この行に注目 */

main()
{
        S_samp cpro1 = {"C-Production No.", 116, "2001/05/16"},
               cpro2 = {"C-Production Ex.", 2, "2001/05/20"};

        printf("%s%03d %s\n", cpro1.title, cpro1.no, cpro1.date);
        printf("%s%03d %s\n", cpro2.title, cpro2.no, cpro2.date);

        cpro1 = cpro2;

        printf("%s%03d %s\n", cpro1.title, cpro1.no, cpro1.date);
        printf("%s%03d %s\n", cpro2.title, cpro2.no, cpro2.date);

}

◇構造体の演習

1.構造体の定義と宣言

構造体 st_test を定義します。

struct st_test{
int i,j;
char c;
float f;
};

 ここで注意しなければならないのは中括弧閉じ(})の後にセミコロン(;)をつけることです。

なぜなら、関数ではないから・・・。
短くしてみるとこんな感じになります。

struct st_xx{…} ;

また、構造体の中で宣言されている変数等はメンバと呼びます。
ここでは int型の i と j 、char型の c 、float型の f がそれぞれメンバ変数になります。

ただし、この時点では構造体はメモリの確保をしていません。
構造体変数の宣言を行ったとき初めてメモリに確保されます。

次に、構造体の定義と宣言を同時に行う場合の例を示します。

struct st_test{
int i,j;
char c;
float f;
}TEST;

これは、

struct st_test{
int i,j;
char c;
float f;
};

struct st_test TEST; /* 構造体変数の宣言 */

と同じになります。
また、ここ以外のところでこの構造体変数の宣言を行わないのなら、構造体名(ここではst_test)を省略することも可能です。

struct{
int i,j;
char c;
float f;
}TEST; /* この場合ここ以外のところでこの構造体変数の宣言はできない */

カンマ(,)で区切ることにより定義時に複数宣言することも可能です

struct{
int i,j;
char c;
float f;
}TEST1,TEST2,TEST3;

さらに、typedef を用いて構造体型を新規に定義すれば宣言が楽になります。

typedef の使い方

typedef long INT_32 ; /* long 型を INT_32型として定義します。 */
typedef short INT_16 ; /* short 型を INT_16型として定義します。 */

これを元に構造体に適用してみると・・・

typedef 構造体の定義全体 新しく定義する型 ;

となり、具体的に構造体 struct st_test を ST_TEST として定義する場合

typedef struct st_test{
int i,j;
char c;
float f;
}ST_TEST;

となります。
(見た目はさっきと似てますが ST_TEST は変数ではなくデータ型です)
こうすれば、この構造体の変数を宣言するとき

ST_TEST st1;

という書き方ができるようになります。

2.初期化

構造体変数の初期化は配列のような方法で初期化できます。

ST_TEST 型 (struct st_test) の st1 をで宣言時に初期化してみます。
配列と違って異なる型で構成されていますが問題ありません。

struct st_test st1 = {0, 0, ‘A’, 1.00};

ST_TEST st1 = {0, 0, ‘A’, 1.00};

下は typedef を利用した場合です。また、定義時の宣言でも初期化は可能です。

3.参照・代入

今度は構造体を使う時の説明です。

構造体のメンバ変数にアクセスするときはドット(.)を使います。

たとえば、ST_TEST 型の構造体 st1 のメンバ変数 i にアクセスしたいときは

st1.i

とします。以下同様に j,c,f は

st1.j
st1.c
st1.f

となります。
代入時も同じようにメンバ変数を参照して代入します。

st1.i = 3 /* 構造体 st1 のメンバ変数 i に3を代入 */

代入では初期化の時のようにメンバすべてを一括代入することは出来ません。
(これは配列でも同じですね)

4.構造体の複写

同じ構造体型の変数同士の場合、メンバ全てを一括複写することが可能です。

st1 = st2 ;

とすれば st2 の全てのメンバの値が st1 に複写されます。

◇構造体の演習(ポインタをメンバに含んだ場合)

1.構造体の定義と宣言

typedef struct st_test{
int *p;
int i,j;
}ST_TEST;

int a = 3;
ST_TEST s; /* 構造体の宣言 */
s.p = &a; /* ここで 変数a のアドレスを設定 */

宣言自体はポインタを含んでも大したことはありません。
ただ、メンバ中のポインタにアドレスを設定する前に、対象となる変数の宣言を行う必要があるくらいです。
これは通常のポインタでも同じ事ですが、以外と構造体では忘れがちかな?と思いましたので付加えてみました。

ちなみにメンバにポインタを含む構造体の初期化は見事に失敗しました。

ST_TEST s = {10,20,&a};

とすると &a のところでエラーがでます。
&a はアドレス値なので初期化時に使えると思ったのですが構造体と配列の初期化には無理なようです。(BCC5.5で確認)
なお、ただのポインタの初期化には問題ありません。

int *point = &a ; /* これはOK */

2.参照

次に、構造体のメンバを使ってアクセスしてみます。

*s.p = 2;

これで変数 a に2が代入されました。
ところでこの *s.p は次のどちらでしょう。

1.*(s.p)
2.(*s).p
3.s->p

答えは1番です。
2番目は構造体そのものがポインタになっている場合で
今回の場合とは違います。それについては後々説明します。
ちなみに3番は2番と同じ意味です。
(今、解説すると間違い無く混乱の元になるので、後にします。)