10.
構造体
構造体
・*:..☆ プロローグ ☆..:*・
むかしむかし、ドイツの北の方のある村に一匹のロバがいました。ロバは年を取って力がなくなっていたので荷物運びの労働を辛く感じていました。そんな時にブレーメンという町で音楽隊を募集していると言う噂を聞いたので、雇ってもらおうと思いました。
ロバは、主人のところを出てブレーメンに行き、音楽隊募集事務所を訪ねました。
「こんにちは。音楽隊を募集しているって聞いたのですが。」
「ああ、音楽隊を募集しているよ。正確には、“構造体ブレーメン音楽隊”だけど。」
「何ですか、その構造体って?」
「構造体っていうのは、いくつかの型を組み合わせた独自の型さ。ブレーメン音楽隊のメンバーは4人でね、それで...」
「わかりました。メンバーは4人ですね。」
「あ、ちょっと待って...」
ロバは年を取って気が短くなっていたので、話を最後まで聞かずに飛び出して行き、村に戻ってメンバーを探しました。そして、ロバと同じように年を取ったイヌ、ネコ、オンドリが加わりました。メンバーが揃ったので再びブレーメンに行こうとしましたが、途中の森で日暮れになってしまったので、その日は森の中で寝ることにしました。
4匹が寝るところを探していると、オンドリが一軒の家を見つけました。ロバが家の中をのぞくと、ドロボウたちが、おいしそうに食事をしています。それを見てうらやましくなった4匹は、化け物のふりをしてドロボウを追い出し、食事と寝床を確保しました。
そして翌日、再びブレーメンの音楽隊募集事務所を訪ねました。
「こんにちは。メンバーが揃いました。ソプラノのネコ、テノールのオンドリ、バリトンのイヌ、そしてバスのロバです。」
「うーん、困ったな。“構造体ブレーメン音楽隊”のメンバーは、ソプラノ、アルト、テノール、バスなんだよ。すまないが、型には厳密でないとね。」
年を取って、あきらめの良くなっていた4匹は音楽隊を断念し、ドロボウを追い出した家に戻り、ずっと暮らしたということです。
構造体とは、関連のあるいくつかのデータを、一つにまとめて扱えるようにしたものです。
例えば、次のようなデータは密接な関連があると考えられます。
- 平面上の x 座標と y 座標
- 住所録における、氏名、住所、電話番号
- 日付を特定する、年、月、日
日付を特定する場合について考えてみましょう。
日付は、年、月、日の整数3つによって、特定されるものとします。“1999年12月25日”のようにです。
よって、日付を特定するために変数を用意する場合には、3つの変数が必要です。
・年を表す整数 year
・月を表す整数 month
・日を表す整数 day
ある関数があり、その関数は、日付のデータを引数とするものであるとしましょう。その場合にも3つの引数が必要になります。
関数名( 整数 year,整数 month,整数 day )
配列についても考えてみましょう。複数の日付を扱うため配列にするのであれば、整数の配列が3つ必要となります。
・年を表す整数の配列 array_year
・月を表す整数の配列 array_month
・日を表す整数の配列 array_day
このように、日付を特定するためには、常に3つの整数が必要となるのです。
さてここで、年、月、日の3つの整数を一組にしてみましょう。そして、その組み合わせに“日付”という名前を付けることにします。
これは、変数名のように、実体、つまり、実際に値を持つことが出来るものに対して付けられた名前ではなく、データの組み合わせ方に対して付けた名前です。
日付
|
|
年を表す整数 year
月を表す整数 month
日を表す整数 day
|
すると、データの組み合わせ、“日付”を使用することにより、変数を1つにすることが出来ます。
・日付 date
関数の引数も1つに出来ます。
関数名( 日付 date )
配列の場合も同様です。
・日付の配列 array_date
このように、データの組み合わせを作ることによって、プログラムを簡潔に記述できるようになります。
そして、この“データの組み合わせ方”が構造体であり、構造体は、自分で定義できるデータ型なのです。
さらに、単なる3つの整数ではなく、日付を特定するという明確な目的が与えられていることによって、プログラムが分かりやすくなるのです。
構造体と同様の仕組みを持つプログラミング言語は少なくありません。
また、構造体の考え方は、オブジェクト指向プログラミングのクラスにも通ずる重要なものです。
10.1. 構造体の定義
構造体の定義
構造体を使用する方法は、いくつかあるのですが、ここでは一般的と思われる方法を中心に説明します。
構造体は、定義することによって使用できるようになります。構造体の定義とは、関連のあるデータの組み合わせを決め、それに名前を付けることです。
定義した構造体は、データ型の1つとして扱えるようになります。
構造体の定義は、以下のように記述します。
struct 構造体タグ
{
メンバー・リスト
};
- 構造体の定義は“struct”キーワードから始まります。
-
“struct”の後に“構造体タグ”を記述します。これが、構造体の名前です。
-
続く波カッコ { } の中に、変数の定義を記述します。この変数を“メンバー”または、“メンバー変数”と呼びます。変数の定義は、変数宣言と同じように記述します。
データ型 変数名;
- 構造体には、少なくとも1つのメンバーが必要です。
- メンバーのデータ型として、構造体を使用することも出来ます。
- 最後にセミコロン ‘;’ を記述します。
地球上の位置を表す、緯度と経度をメンバーに持つ構造体の例です。
struct POSITION
{
double latitude;
double longitude;
};
構造体の定義は、変数宣言と同じようにブロックを形成する波カッコ { } の先頭で行えますが、通常は、関数定義の外側で行います。
struct POSITION
{
double latitude;
double longitude;
};
main( )
{
}
この位置に記述することによって、複数の関数で同じ構造体を使用することが出来ます。ソース・ファイルが複数になる場合には、ヘッダー・ファイルに記述するのが一般的です。
構造体の変数
構造体の変数
定義した構造体は、int、double 等の基本的なデータ型と同じように、変数を宣言して使用することが出来ます。
構造体の変数を宣言するには、次のように記述します。
struct 構造体タグ 変数名;
“struct 構造体タグ”が、データ型の名前になると考えてください。
構造体 POSITION の変数宣言は、次のようになります。
struct POSITION pos;
配列やポインタ変数の場合も、基本的なデータ型の場合と同様です。
struct POSITION aPos[12];
struct POSITION* pPos;
構造体の定義を伴う変数宣言
構造体の定義を伴う変数宣言
あまり使用される方法ではありませんが、次のように構造体の変数を宣言することも出来ます。
void main( void )
{
struct
{
double latitude;
double longitude;
} pos;
}
このように記述すると、2つのメンバーを持った構造体の変数 pos の宣言が出来ます。
この場合には構造体タグを省略してかまいません。
10.2. メンバーの参照
メンバーの参照
プログラム中で構造体のデータを使用するためには、メンバーの参照が出来なければなりません。
構造体のメンバーを参照するには、以下のように記述します。
変数名.メンバー変数名
構造体の変数名の後に構造体メンバー演算子であるピリオド ‘.’ を記述し、その後に、その構造体のメンバー変数名を記述します。
構造体 POSITION のメンバーに値を代入し、表示する例です。
struct POSITION pos;
pos.latitude = 35.65861;
pos.longitude = 139.745447;
printf( "緯度:%f、経度:%f\n", pos.latitude, pos.longitude );
- 変数 pos のメンバーである latitude、longitude に値を代入しています。
- printf を使用し、latitude、longitude の値を表示しています。両方とも double 型であるため、“%f”を使用します。
10.3. 構造体の扱い方
構造体の扱い方
定義した構造体は、基本的なデータ型と、ほとんど同じように扱うことができます。
構造体 POSITION を例に見てみましょう。
変数の初期化
変数の初期化
構造体の変数を初期化するには、波カッコ { } の中にメンバー変数の値を記述します。記述する順番は、構造体の定義と同じにします。
struct POSITION pos = { 35.41, 139.45 };
printf( "東京→%f、%f\n", pos.latitude, pos.longitude );
配列を初期化する場合には、波カッコ{ }を二重に使用します。
struct POSITION aPos[] ={ { 35.41, 139.45 },
{ 34.41, 135.29 } };
printf( "東京→%f、%f\n", aPos[0].latitude, aPos[0].longitude );
printf( "大阪→%f、%f\n", aPos[1].latitude, aPos[1].longitude );
代入演算子
代入演算子
構造体の変数に対して、算術演算子や関係演算子は使用できません。演算子を適用した結果が確定できないことから、お分かりいただけると思います。
これに対し、代入演算子の“=”は、構造体の変数にも使用できます。“=”演算子は、構造体の全てのメンバーをコピーします。
struct POSITION posTokyo ={ 35.41, 139.45 };
struct POSITION pos;
pos = posTokyo;
printf( "緯度:%f、経度:%f\n", pos.latitude, pos.longitude );
関数の引数
関数の引数
構造体を関数の引数とする例です。
void PrintPosition( struct POSITION pos )
{
printf( "緯度:%f、経度:%f\n", pos.latitude, pos.longitude );
}
void main( void )
{
struct POSITION pos ={ 35.41, 139.45 };
PrintPosition( pos );
}
- 引数が値渡しであるのは基本的なデータ型と同じで、呼び出し側の変数のコピーが渡されます。
関数の戻り値
関数の戻り値
構造体を関数の戻り値とする例です。
struct POSITION GetTokyo( void )
{
struct POSITION pos ={ 35.41, 139.45 };
return pos;
}
void main( void )
{
struct POSITION pos;
pos = GetTokyo();
printf( "緯度:%f、経度:%f\n", pos.latitude, pos.longitude );
}
構造体の配列
構造体の配列
基本的なデータ型と同じように角カッコ [ ] を使用します。
struct POSITION aPos[2];
aPos[0].latitude = 35.41;
aPos[0].longitude = 139.45;
aPos[1].latitude = 34.41;
aPos[1].longitude = 135.29;
printf( "東京→%f、%f\n", aPos[0].latitude, aPos[0].longitude );
printf( "大阪→%f、%f\n", aPos[1].latitude, aPos[1].longitude );
構造体をメンバーとする構造体
構造体をメンバーとする構造体
構造体をメンバーとする構造体の例です。
struct FROMTO
{
struct POSITION from;
struct POSITION to;
};
以下のように、変数 fromto があった場合、
struct FROMTO fromto;
次の記述が参照するのは、位置を表す POSITION 型のメンバーです。
fromto.from
fromto.to
次の記述が参照するのは、緯度、経度を表す double 型のメンバーです。
fromto.from.latitude
fromto.from.longitude
fromto.to.latitude
fromto.to.longitude
10.4. 構造体とポインタ
構造体とポインタ
構造体の変数についても、基本的なデータ型の変数と同じようにポインタを使用することが出来ます。
struct POSITION pos;
struct POSITION* ppos;
ppos = &pos;
ポインタ変数 ppos から、メンバーを参照するには、演算子“*”を使用して次のように記述します。
(*ppos).latitude = 35.41;
(*ppos).longitude = 139.45;
演算子の優先度から *ppos を丸カッコ ( ) で囲まなければなりません。
構造体のポインタ変数からメンバーを参照するには、もう一つの方法が用意されています。
マイナス記号‘-’と大なり記号‘>’を組み合わせた矢印のような演算子“->”を使用することにより、構造体のポインタ変数から、演算子“*”を使用せず、メンバーを参照することが出来ます。
ppos->latitude = 35.41;
ppos->longitude = 139.45;
もちろん、どちらの方法でも結果は同じなのですが、記述が容易であることから“->”を使用する方が一般的です。