構造体


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

構造体

構造体とは

構造体とは、一言でいってしまえば内部に複数の値を持つ値です。
これによって、複数の値を一つの値として扱うことが可能になります。
これは非常に重要な事で、例えば関数は一つの値しか返すことができませんが、これにより実質的に複数の値を返すことが可能になります。
また複数の関連性のある値をまとめて管理することも可能になります。
例えば、二次元座標はx軸の値とy軸の値の二つの値を持つ物として以下のように定義できます。
struct Point{
   double x, y;
};
また、キャラクターデータなどは以下のように定義できます。
struct Character{
   struct Point pt; /* キャラクターの存在する座標 */
   char[] name; /* キャラクターの名前 */
   int life, ....... /* 生命力などといったステータス */
};

値の操作

カンマ演算子を使います。
struct Point pt;
pt.x = 0;
pt.y = 0;
int x = pt.x, y = pt.y;
アドレス経由で操作する時のために、アロー演算子という物が用意されています。
struct Point *pt_p = &pt;
(*pt_p).x = 2; /* アロー演算子を使わない場合。書きにくい。 */
pt_p->y = 3; /* -> これがアロー演算子。矢印演算子などとも。 */
x = pt.x; /* x には 2 が代入される */
y = pt.y; /* y には 3 が代入される */

関数での取扱い

関数で取り扱う際などに注意しないといけない事があります。
それは構造体は一般的に「大きい」ということです。
どういうことかというと、例えば下のような構造体の場合、int や double といった組み込み型に比べてメモリを多く使用するということです。
struct SugoiOokiiMono{
   int a[1000]; /* すごいおおきいよ! */
};
このような大きな値は、当然複数存在すればそれだけメモリ容量は圧迫されますし、コピーするだけでもそれなりの時間的なコストが発生してしまいます。
こういった問題を避けるために、構造体はそれ自体の値ではなく、アドレスの値で受け渡しすることが多くなります。
例えば、関数から構造体を返したいときは、値をそのまま返すのではなく、引数に結果の受け渡し用の構造体のアドレスを渡したりします。
struct SugoiOokiiMono normal(struct SugoiOokiiMono som){
   ...; /* som に対する操作 */
   return som;
}
struct SugoiOokiiMono *bad(struct SugoiOokiiMono *som){
   struct SugoiOokiiMono ret;
   ...; /* ret に対する操作 */
   return &ret;
}
void better(struct SugoiOokiiMono *som, struct SugoiOokiiMono *ret){
   ...; /* ret に対する操作 */
}
上の関数 normal の場合、引数を渡すときと値を返すときの二回、構造体の値のコピーが行われます。
真ん中の関数 bad は正しくない関数です。関数から抜けた時点で ret は存在しなくなるため、ret のアドレスを返す事は間違いです(関数の外で使おうとするころには、関数の返すアドレスの先にはすでに有効な値は存在しないのでエラーになります)。
下の関数 better の場合、構造体の値のコピーは一度も発生しません。
とはいえ、常に下のように書かないといけないかというと、そういうことでもありません。小さい構造体に関していえばコピーに大したコストはかからないでしょうし、またその関数が精々数回しか呼ばれないことが分かっているような場合もよほど大きい値でなければ問題ないでしょう。
しかし両方の方法を同時に使うことは混乱の原因にもなるため、一貫性を保つためにもどちらかに統一すべきでしょう。

関数による値の操作

構造体の値の操作は常に関数によって行われるべきです。
例えばこういった関数ですね。
double getPointX(struct Point *p){ return p->x; } /* 値を得る関数。 */
void setPointX(struct Point *p, double x){ p->x = x; } /* 値を書き換える関数 */
理由は、構造体の仕様変更を考えれば分かります。
例えば何らかの理由で、構造体の内部変数 x y の名前が i j に変更になった場合、カンマ演算子やアロー演算子で直接操作していた場合、操作していたすべての部分を書き換えないといけなくなります。書き換えたすべてのソースコードを再コンパイルする必要があります。
一方、関数によって操作していた場合、関数の内部を書き換えるだけですみます。再コンパイルも関数を定義しているソースコードだけですみます。
今回の例はあまり現実的ではありませんが、こういったことは現実には頻発することです。特に複数人による開発では、多少面倒でもできる限り上記のような関数(アクセサと呼ばれたりすることもあります)を書くべきです。
また、アクセスすべきではない値などに対してアクセサを書かなければ、コメントではなくコードで「アクセスすべきではない」という情報を知らせられるなどといった効果もあります。
とはいえ、今回の例のような二次元座標を表すだけの構造体なら、どう考えても内部構造が変更されることはないので、アクセサは必要ないとは思いますが…