構造体とは、複数の異なる型を一塊として扱えるものです。配列では同じ型しか持つことができません。そこが、構造体のよいところです。
構造体と聞くと、宣言が面倒だとか、メンバ変数への記述が長くなるから嫌だとか、いろいろあると思います。
もちろん、構造体を使わなくても、プログラムは書けます。ただ、関連性のあるデータを一塊にして扱えるほうが便利な時があります。
嫌がっていたら、いつまでたっても使えないままになるので、とりあえず作ってみましょう。
構造体の作り方
まず、構造体の作り方からです。構造体の定義は次のようになります。
struct タグ名 { メンバ変数1; メンバ変数2; ・ ・ ・ };
structを先頭につけ、構造体の名前をつけます。これをタグ名と呼んでいます。
そして、その構造体に属するメンバ変数を宣言します。メンバ変数は中カッコ{}で囲みます。よく忘れがちなのが、最後の「;(セミコロン)」です。
関数を作っているイメージなってしまうため、意外と忘れます。忘れると、コンパイルエラーになります。
メンバ変数の型は何でもOKです。また、メンバ変数を配列で持つこともできますし、別の構造体を持つことも可能です。
ここで、メンバ変数の型は何でもOKとお話ししました。ただ、必須ではないのですが、なるべく型のサイズを意識して、並べるようにしましょう。
なぜなら、組み込みマイコンでは、限られた容量を有効に使わないといけないためです。
ちょっと例を示します。
struct st_a { char m_char_data1; long m_long_data1; char m_char_data2; short m_short_data1; short m_short_data2; long m_long_data2; }; struct st_b { long m_long_data1; long m_long_data2; short m_short_data1; short m_short_data2; char m_char_data1; char m_char_data2; };
上記の2つの構造体があったとします。どちらも、long型、short型、char型のメンバを2つずつ持っていますね。
しかし、この2つの構造体の使用領域は異なります。
なぜなら、longなら4バイト、shortなら2バイト、charなら1バイトですよね。long型は4の倍数のアドレス、short型は2の倍数のアドレス、char型は1の倍数のアドレスに配置されるためです。
構造体st_bのほうは、順番に型の大きい順に並んでいるので、アドレスの始めが「0」だとすると、メンバのアドレス配置は、
0:m_long_data1 4:m_long_data2 8:m_short_data1 10:m_short_data2 12:m_char_data1 13:m_char_data2
となり、全部で14バイトで済みます。
ところが、構造体st_aはメンバの型がばらばらに並んでいるので、
0:m_char_data1 4:m_long_data1 8:m_char_data2 10:m_short_data1 12:m_short_data2 16:m_long_data2
となり、全部で20バイト必要になり、6バイト分無駄になってしまいます。char型のあとにlong型を並べたら、間に最大3バイトの使用されない領域が出てしまいます。
先ほども、お話したように、組み込みマイコンでは容量が限られています。例では、たった6バイトかもしれませんが、されど6バイトなのです。
もし、構造体の配列を宣言したら、どうなるか、もうおわかりですね。要素数×6バイト分の容量が無駄になってしまうのです。
したがって、メンバの型を意識して、並べたほうがよいということが、おわかりいただけたと思います。
構造体の使い方
構造体を作ったら、それを使わないと意味がありませんね。ここでは、構造体の使い方をお話していきます。
まず、先ほどの構造体st_bを使っていきたいと思います。
構造体を使用するためには、通常の変数と同様、宣言してあげる必要があります。
struct st_b value
特別、何か必要ということはありません。
struct タグ名 変数名;
でいいわけです。
今度は、各メンバ変数へのアクセス方法ですが、構造体のメンバ変数へアクセスするには、
value.m_char_data1
とすれば、代入も、値の読み出しもできます。
value.m_char_data1 = value.m_char_data2;
のように構造体のメンバ同士での代入処理も当然可能です。
このように、「構造体変数名.構造体メンバ変数」とすれば、char型やshort型などで宣言された変数と同じように扱うことができます。
構造体の初期化
構造体を作って、使い方がわかったら、構造体の初期化をしてみましょう。
正確には構造体のメンバ変数の初期化をしましょうということです。
「構造体変数名.構造体メンバ変数」で構造体のメンバ変数にアクセスできることをお話しましたね。
これが、わかっていれば、初期化は簡単です。各メンバごとに初期化をすればいいだけです。
例えば、すべて0で初期化する場合は、以下のようになります。ここも構造体st_bでいきます。
#include <stdio.h> /* 構造体定義 */ struct st_b { long m_long_data1; long m_long_data2; short m_short_data1; short m_short_data2; char m_char_data1; char m_char_data2; }; void main(void) { struct st_b value; /* 構造体変数 */ /* 構造体メンバ初期化 */ value.m_char_data1 = 0; value.m_char_data2 = 0; value.m_short_data1 = 0; value.m_short_data2 = 0; value.m_long_data1 = 0; value.m_long_data2 = 0; }
意味はないプログラムですが、初期化はこれだけです。
メンバに配列があっても、for文で回して初期化してあげればいいだけです。
すべて0での初期化なら、標準関数memsetを使う方法もあります。
#include <stdio.h> #include <string.h> /* 構造体定義 */ struct st_b { long m_long_data1; long m_long_data2; short m_short_data1; short m_short_data2; char m_char_data1; char m_char_data2; }; void main(void) { struct st_b value; /* 構造体変数 */ /* 構造体メンバ初期化 */ memset((void*)&value, 0, sizeof(value)); }
memsetを使うにはstring.hをインクルードする必要があります。
第1引数に、セットする変数のポインタ、第2引数に、セットする値、第3引数に、第2引数にセットした値にするサイズを指定します。
サイズを指定するのにsizeof(value)としています。こうすることで、構造体変数valueで使用するサイズを指定することができます。
まとめ
構造体の作り方と使い方をお話しました。難しいと感じましたか?
ちょっとしたひと手間で、データを扱いやすくなります。プログラム例では、適当な変数にしましたが、実際には各個人の成績結果だったり、住所データだったり、いろいろ使う機会があります。
ぜひ、作るのを面倒くさがらずに、構造体を使っていってほしいと思います。