[←1つ前] [→1つ後] [↑質問一覧] [↑記事一覧] [ホームページ]
struct axis {
int x;
int y;
};
struct axis lt = {0, 0};
構造体の中にさらに構造体が含まれるような場合には、{}を使って、対応する
構造と同じように初期値を並べます。
struct area {
struct axis lefttop;
struct axis rightbottom;
};
struct area window = {{0, 0}, {1023, 767}};
ただし、解釈に曖昧さがなければ、構造の順番に初期値を代入する場合におい
ては、途中の{}を省略することができます。この場合においても、一番外の{}は
省略することはできません。
struct area window = {0, 0, 1023, 767};
構造体と配列が複合した場合も同様です。
構造体の要素よりも初期値が少ない場合は、仕様として許されます。この場合 は、初期値が指定されていない残りのメンバーは0に初期化されます。
struct area window = {{0}, {1023, 767}};
/* {{0, 0}, {1023, 767}} を指定したのと同じ */
コンパイラによっては、警告が一定数を超えた場合にはコンパイルを中断する ような機能を持っていることがあります。例えばBorland C++がそうです。{}を省 略した初期化を行うプログラムをこのようなコンパイラでコンパイルすると、場 合によっては非常に多くの警告が表示されるため、このチェックにひっかかって 中断してしまうことがあります。特定の警告を出ないようにするか、警告が多い 場合に処理を中断しないように指定することで問題を回避することができます。
struct area window = {0, 0, 1023, 767,};
struct area window = {
0,
0,
1023,
767,
};
struct axis {
int x;
int y;
};
struct axis pos;
pos = {640, 480};
foo()
{
struct axis pos;
...
{
struct axis tmp = {640, 480};
pos = tmp;
}
...
}
もっと簡単に書くには、初期値を代入した外部変数を用意しておく方法もあり
ます。
const struct axis INITIAL_VALUE = {640, 480};
foo()
{
struct axis pos;
...
pos = INITIAL_VALUE;
...
}
この場合も、初期値が入った構造体があらかじめメモリ上に確保される時に多
少は冗長なコードになる可能性はありますが、殆ど無視できる程度だと思われま
す。定数を複数の箇所で使う必要があるなら、外部変数として用意することにな
りますが、その場合は名前の衝突に常に気を付ける必要があります。
関数内でのみ使うのであれば、局所変数として用意するとよいでしょう。
foo ()
{
static const struct axis INITIAL_VALUE = {640, 480};
struct axis pos;
...
pos = INITIAL_VALUE;
...
}
staticで宣言しなくても動作は同じですが、
INITIAL_VALUEへの代入が関数呼び
出し毎に発生するため、オーバーヘッドが気になります。
初期化のための関数を用意するという方法もあります。
struct axis set_struct_axis(int x, int y)
{
struct axis pos;
pos.x = x;
pos.y = y;
return pos;
}
foo ()
{
struct axis pos;
...
pos = set_struct_pos(640, 480);
...
}
初期化の所のコードは多少見やすくなりますが、この場合も、本当に関数を呼
び出すコードが生成されるとオーバーヘッドが気になるところですが、コンパイ
ラによってはインラインに展開するような書き方ができる場合もあります。
(参考 comp.lang.c FAQ 2.10)
struct array {
int item[10];
};
・構造体は、関数の引数、あるいは戻り値として使うことができ、この場合は 値が渡される。
配列を関数の引数に指定することもできますが、関数の引数に配列を与えた場 合には、配列の内容ではなく配列の先頭を指すポインタが渡されます。呼び出さ れた関数の中で配列の要素を変更すると、呼び出した関数における配列と同じオ ブジェクトが変化することになります。構造体を関数呼び出しの引数に与えると、 渡されるのはその構造体の値のコピーですから、呼び出された関数の中で何があ っても呼び出した関数の構造体の内容は変化しません。また、関数からの戻り値 として構造体が指定されていれば、その構造体の内容を呼び出した側で受け取る ことができます。
・同じ型の構造体は、代入演算子を使って代入することができる。
配列は、たとえ要素の数が同じであっても、「=」で内容をそっくりコピーする
ことはできません。memcpyのような関数を使うことが必要です。
構造体の場合は、「=」を使って内容をコピーすることができます。
さて、配列を一つだけ含んだ構造体の意図ですが、おそらくこのどちらかの機
能を使うためのテクニックでしょう。例えば、配列のままだと、内容をコピーす
るにはmemcpyのような関数を使う必要がありますが、
構造体にすることによって、代入演算子だけでコピーするように書くことができます。
/* 配列のコピー */
foo()
{
int a[10], b[10];
...
memcpy(a, b, sizeof(a));
...
}
/* 構造体のコピー */
foo()
{
struct array a, b;
...
a = b;
...
}
struct st {
int first;
char second;
int third;
};
struct st a;
struct st b:
memcpy(&a, &b, sizeof(a)); /* 正常にコピーされない? */
/* コピーするための関数例 */
void st_cpy(struct st *dest, struct st *src)
{
dest->first = src->first;
dest->second= src->second;
dest->third = src->third;
}
memcpyで全く正しく
コピーされるはずです。
わざわざコピーするための関数を作るのは無駄を増やす以外のなにものでもあり
ません。
もちろん、このような場合はmemcpyを使うという選択でさえ
余計な手間をかけてコードを書くことになります。
a = b;
と書けば済むのですから。ただし、ANSIに準拠していない一部のコンパイラの
中には、構造体の代入を許さないものもあります。そのようなコンパイラを使う
場合には、memcpyを使ってコピーする必要がある場合もあります。
a = b;のようにして構造体をコピーした場合には、メンバーの中
にポインタが含まれていると、その実体ではなくポインタの値がコピーされるこ
とに注意する必要があります。コピーした結果、二つの構造体のポインタが共通
の一つのオブジェクトを指すことになります(NULLならば何も指しません)。この
ため、一方の構造体に含まれているポインタの指す先を変更すると、もう一方の
構造体に含まれているポインタの指す先も、同時に変化してしまいます。
構造体のコピーではなく比較をmemcmpで行うことは避けるのが
無難です。memcmpで比較を行うと、要素が全て同じ値であっても、
必ずしも0という値が戻って来る
とは限りません。なぜなら、構造体の要素は連続しているとは限らないからです。
要素に隙間があると、構造体の各要素の値が同じでも、隙間のごみの内容が異な
ると、memcmpが一致しないという結果を戻すかもしれません。
memcpyの場合は間のごみもまるごとコピーするので別に問題には
ならないはずです。
struct table {
int number;
char *key;
};
struct table a, b;
...
a = b;
構造体を複製したつもりだったが、a.keyを変更すると
b.keyまで変わってしまう。
a.keyも複製するにはどうすればよいか。
keyの指す先が文字列であるとします。
a = b;
a.key = malloc(strlen(b.key) + 1);
strcpy(a.key, b.key);
実際は、エラーチェックを行う必要があります。すなわち、b.key
がNULLでないことを確認して、malloc
の戻り値もNULLでないことを確認して、その後で
strcpyを実行するようにします。
特に注意すべき箇所としては、intのサイズの差や、文字コードの違い、利用で きるメモリの量などです。
Windows環境で動作するプログラムの場合、DOSと根本的にライブラリ等が異な りますので、書き直すことになるでしょう。
最初からマルチプラットフォームでプログラムを利用するつもりであれば、そ のような開発環境を使うという選択も検討することがあります。
NULLは0と比較するような場合に
は常に一致することが保証されています。言いかえれば、NULL == 0
という式は、常に1という値になるはずです。
処理系によっては、NULLポインタの実装上の値が0
でないかもしれませんが、Cのプログラムから見た場合には、内部表現にかかわらず、
0という値はNULLと一致することが保証されるように
コンパイラがうまくやってくれることになっています。
->」の意味を理解するには、構造体とポインタの両方を理
解しなければならないので、C言語を使いはじめた人には難しいかもしれません。
要するに、
(*p).x
と
p->x
というのは全く同じ意味です。
すなわち、ある構造体の領域を指すポインタpがある時、
*pは構造体の実体を意味しますから、そのメンバーである
xを表現すると(*p).xとなります。
p->xという表現は、これと全く同じ意味として解釈されます。
struct axis *p;
p = malloc(sizeof(struct axis));
p->x = 800;
p->y = 600;
struct axis
p -------→ +----------+
| 800 | x
+----------+
| 600 | y
+----------+
(*p).x と p->xが全く同じであるなら、
なぜこのような書き方が特別に用意されているのか。
(*p).xとp->xは全く同じ意味になるので、
「->」という表現がなくてもプログラムは書けます。
にもかかわらず、特別に演算子が用意されているのは、
プログラム中にこのようなデータ構造がしばしば現れるという事実が理由になっ
ているようです。K&Rにも「構造体へのポインタはよく使われる」という記述があ
りますが、特に、構造体の領域をmallocで獲得したり、
関数への引数として構造体を指定する場合には、構造体そのものではなく、
ポインタだけを渡すことがよくあります。
(*p).xと書く場合には括弧を省略することができません。
なぜなら、演算子
「*」よりも「.」の方が優先度が高いので、
*p.xと書いた場合には*(p.x)と解釈されてしまうか
らです。幸い、この書き方はコンパイルした時にエラーとなるので発見できますが、
(*p).xという書き方は、括弧が必要であるため他の括弧と重なり、
プログラムを読みにくくする原因となりがちです。そこで、見やすいプログラム
を簡単に書けるように「->」という表現が用意されたのでしょう。
「->」という書き方は、ポインタの指す先の領域を矢印で示
しているのだと思えば、比較的イメージしやすいと思います。
※ c.l.c FAQ : comp.lang.c FAQ list
http://www.eskimo.com/~scs/C-faq.top.html
文中の項目番号は新しい版に対応しています。旧版とは異なります。