「K&R プログラミング言語C」の 「6.1~6.2 構造体の基本と関数」でばらばらに紹介されていた関数をまとめて、main 関数で具体的な数値を与えてコンパイル、実行してみました。
まず、平面上の座標 ( x, y ) を持つ構造体で点を表し、これを入れ子にして二つの対角点 ( pt1, pt2 ) を持つ構造体で長方形を表します。
/* 点: point(x, y) 座標 */
struct point {
int x;
int y;
};
/* 長方形: rect 対角点の座標~構造体の入れ子 */
struct rect {
struct point pt1;
struct point pt2;
};
struct point makepoint(int, int)
は、引数の二つの整数から点の座標を持つ構造体を作る関数です。
/* makepoint: x および y 座標から点を構成する */
struct point makepoint(int x, int y)
{
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}
1. メンバーアクセス演算子 .
構造体のメンバーアクセス演算子には, .
と ->
がありますが、まず
構造体名.メンバー名
を使って、二つの座標の中点 ( middle
)、差 ( diff
)、距離 ( dist
)、和 ( sum
) を求めてみます。
struct rect screen;
struct point middle, sum, diff; //中点、和、差の座標
/* 長方形の中点(小数点以下切り捨て) */
middle = makepoint((screen.pt1.x + screen.pt2.x) / 2,
(screen.pt1.y + screen.pt2.y) / 2);
/* 長方形の座標間の距離 */
double dist, sqrt(double);
diff = subpoint(screen.pt1, screen.pt2);
dist = sqrt((double)(diff.x * diff.x + diff.y * diff.y));
/* 長方形の座標の和 */
sum = addpoint(screen.pt1, screen.pt2);
/* addpoint: 二つの点を加える */
struct point addpoint(struct point p1, struct point p2)
{
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
/* subpoint: 二つの点を引き算する */
struct point subpoint(struct point p1, struct point p2)
{
p1.x -= p2.x;
p1.y -= p2.y;
return p1;
}
ある座標 p が長方形の中にあるかないかを調べる関数
int ptinrect(struct point, struct rect)
を使うための前処理として、
struct rect canonrect(struct rect)
によって、与えられた長方形の対角点の pt1
の各座標が、pt2
の各座標より小さいという標準形式に変換します。
その際に二つの数値を比較して、小さいほうの数値をとる関数 min(a, b)
と大きいほうの数値をとる関数 max(a, b)
をマクロで作成しています。
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
struct rect screen;
screen = canonrect(screen);
/* 座標p(5, 4) は長方形の中にあるか? */
struct point p = {5, 4};
if (ptinrect(p, screen))
printf("座標p(%d, %d)は長方形の中にあります\n", p.x, p.y);
else
printf("座標p(%d, %d)は長方形の中にはありません\n", p.x, p.y);
}
/* ptinrect: p が r の中なら 1、そうでなければ 0 を返す */
int ptinrect(struct point p, struct rect r)
{
return p.x >= r.pt1.x && p.x < r.pt2.x
&& p.y >= r.pt1.y && p.y < r.pt2.y;
}
/* canonrect; 長方形の座標を標準形式にする */
struct rect canonrect(struct rect r)
{
struct rect temp;
temp.pt1.x = min(r.pt1.x, r.pt2.x);
temp.pt1.y = min(r.pt1.y, r.pt2.y);
temp.pt2.x = max(r.pt1.x, r.pt2.x);
temp.pt2.y = max(r.pt1.y, r.pt2.y);
return temp;
}
下記のソースコード ( points.c ) をコンパイルするときには
$ gcc -Wall -o points points.c -lm
のように末尾に -lm
を付けてください。
※ ライブラリのリンクを忘れずに を参照
/* points.c */
#include <stdio.h>
#include <math.h>
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
struct point {
int x;
int y;
};
/* 構造体の入れ子 */
struct rect {
struct point pt1;
struct point pt2;
};
struct point makepoint(int, int);
struct point addpoint(struct point, struct point);
struct point subpoint(struct point, struct point);
int ptinrect(struct point, struct rect);
struct rect canonrect(struct rect);
int main(void)
{
struct rect screen; // 2つの座標を持つ長方形
struct point middle, sum, diff; //中点、和、差の座標
/* 長方形の座標 */
screen.pt1 = makepoint(9, 6);
screen.pt2 = makepoint(1, 2);
printf("pt1の座標 = (%d, %d)\npt2の座標 = (%d, %d)\n\n",
screen.pt1.x, screen.pt1.y, screen.pt2.x, screen.pt2.y);
/* 長方形の中点(小数点以下切り捨て) */
middle = makepoint((screen.pt1.x + screen.pt2.x) / 2,
(screen.pt1.y + screen.pt2.y) / 2);
printf("pt1とpt2の中点の座標 = (%d, %d)\n\n", middle.x, middle.y);
/* 長方形の座標間の距離 */
double dist, sqrt(double);
diff = subpoint(screen.pt1, screen.pt2);
dist = sqrt((double)(diff.x * diff.x + diff.y * diff.y));
printf("pt1とpt2の距離 = %.3f\n\n", dist);
/* 長方形の座標の和 */
sum = addpoint(screen.pt1, screen.pt2);
printf("pt1とpt2の和 = (%d, %d)\n\n", sum.x, sum.y);
/* 長方形の標準化 */
screen = canonrect(screen);
printf("新しいpt1の座標 = (%d, %d)\n新しいpt2の座標 = (%d, %d)\n\n",
screen.pt1.x, screen.pt1.y, screen.pt2.x, screen.pt2.y);
/* 座標p(10, 10) は長方形の中にあるか? */
struct point p = {5, 4};
if (ptinrect(p, screen))
printf("座標p(%d, %d)は長方形の中にあります\n", p.x, p.y);
else
printf("座標p(%d, %d)は長方形の中にはありません\n", p.x, p.y);
}
/* makepoint: x および y 座標から点を構成する */
struct point makepoint(int x, int y)
{
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}
/* addpoint: 二つの点を加える */
struct point addpoint(struct point p1, struct point p2)
{
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
/* subpoint: 二つの点を引き算する */
struct point subpoint(struct point p1, struct point p2)
{
p1.x -= p2.x;
p1.y -= p2.y;
return p1;
}
/* ptinrect: p が r の中なら 1、そうでなければ 0 を返す */
int ptinrect(struct point p, struct rect r)
{
return p.x >= r.pt1.x && p.x < r.pt2.x
&& p.y >= r.pt1.y && p.y < r.pt2.y;
}
/* canonrect; 長方形の座標を標準形式にする */
struct rect canonrect(struct rect r)
{
struct rect temp;
temp.pt1.x = min(r.pt1.x, r.pt2.x);
temp.pt1.y = min(r.pt1.y, r.pt2.y);
temp.pt2.x = max(r.pt1.x, r.pt2.x);
temp.pt2.y = max(r.pt1.y, r.pt2.y);
return temp;
}
- 実行結果
$ ./points
pt1の座標 = (9, 6)
pt2の座標 = (1, 2)
pt1とpt2の中点の座標 = (5, 4)
pt1とpt2の距離 = 8.944
pt1とpt2の和 = (10, 8)
新しいpt1の座標 = (1, 2)
新しいpt2の座標 = (9, 6)
座標p(5, 4)は長方形の中にあります
2. メンバーアクセス演算子 ->
struct point *pp
は、pp
が struct point
という型の構造体へのポインタであることを表します。*pp
は構造体で、(*pp).x
と (*pp).y
はメンバーです。.
の優先度は *
より高いのでカッコは必要です。
※ C 演算子の優先順位と結合規則 を参照
構造体へのポインタ pp
に対しては、メンバーにアクセスする便法として、次の記法が用意されています。
pp-> 構造体のメンバー
例えば次のように使われ、( 1 ) と ( 2 ) は同じ結果を出力します。
struct point origin = {10, 5}, *pp;
pp = &origin;
printf("origin is (%d, %d)\n", (*pp).x, (*pp).y); // (1)
printf("origin is (%d, %d)\n", pp->x, pp->y); // (2)
.
と ->
は左から右へと結合するので、
struct rect r = {{3, 4}, {5, 6}}, *rp;
rp = &r;
とすると、次の四つの式は等価で、いずれも 3
を出力します。( 下の実行結果参照 )
r.pt1.x
rp->pt1.x
(r.pt1).x
(rp->pt1).x
最後に、
struct {
int len;
char *str;
} *p;
という宣言が与えられた場合に、以下の記法の意味を本文から抜粋しました。
++p->len ... p ではなく len を 1 増やす
(++p)->len ... len をアクセスする前に p が 1 増やされる
(p++)->len ... len をアクセスした後に p が 1 増やされる
*p->str ... str が指すものを指示する
*p->str++ ... 指すものが何であれ、アクセスした後に str を 1 増やす
(*p->str)++ ... 1 増やされるのは str の指すもの
*p++-str ... str の指すものをアクセスした後に、p が 1 増やされる
それぞれの場合について printf 文で出力してみました。(*p->str)++
を出力する printf 文を実行すると、segmentation fault エラーが発生したのでコメントアウトしています。
/* strpnt.c */
#include <stdio.h>
struct point {
int x;
int y;
};
struct rect {
struct point pt1;
struct point pt2;
};
int main(void)
{
struct point origin = {10, 5}, *pp;
pp = &origin;
printf("origin is (%d, %d)\n", (*pp).x, (*pp).y);
printf("origin is (%d, %d)\n", pp->x, pp->y);
struct rect r = {{3, 4}, {5, 6}}, *rp;
rp = &r;
printf("r.pt1.x = %d rp->pt1.x = %d (r.pt1).x = %d (rp->pt1).x = %d\n",
r.pt1.x, rp->pt1.x, (r.pt1).x, rp->pt1.x);
struct {
int len;
char *str;
} *p;
struct {
int len;
char *str;
} data[] = {{10, "abc"}, {20, "def"}, {30, "ghi"}, {40, "jkl"}};
p = &data; // 36 行目
// 37 行目
printf("++p->len = %d\n", ++p->len); // 38行目
printf("(++p)->len = %d\n", (++p)->len);
printf("(p++)->len = %d\n", (p++)->len);
printf("*p->str = %c\n", *p->str);
printf("*p->str++ = %c\n", *p->str++);
// printf("(*p->str)++ = %c\n", (*p->str)++);
printf("*p++->str = %c\n", *p++->str);
return 0;
}
実行結果は以下の通りです。
$ ./strpnt
origin is (10, 5)
origin is (10, 5)
r.pt1.x = 3 rp->pt1.x = 3 (r.pt1).x = 3 (rp->pt1).x = 3
++p->len = 11 ...(1)
(++p)->len = 20 ...(2)
(p++)->len = 20 ...(3)
*p->str = g ...(4)
*p->str++ = g ...(5)
*p++->str = h ...(6)
gdb ( デバッガ ) を使って ( 1 ) ~ ( 6 ) の実行結果を確認してみます。gcc コマンドに -g オプションを付けてコンパイルし、実行ファイル内にシンボルテーブルを保存します。
$ gcc -g -Wall -o strpnt strpnt.c
gdb を起動して、p = &data;
の後 ( 38 行目 ) にブレークポイントを設定して、プログラムを実行させます。
$ gdb strpnt
....
(gdb) b 38
Breakpoint 1 at 0x127f: file strpnt.c, line 38.
(gdb) r
Starting program: /home/ .... /strpnt
プログラムが停止した後に p コマンドで data を表示させます。data 配列には四つの構造体 *p が格納されています。
Breakpoint 1, main () at strpnt.c:38
38 printf("++p->len = %d\n", ++p->len);
(gdb) p data
$1 = {{len = 10, str = 0x55555555605e "abc"}, {len = 20, str = 0x555555556062 "def"}, {len = 30, str = 0x555555556066 "ghi"}, {len = 40, str = 0x55555555606a "jkl"}}
p コマンドで ++p->len
を表示させます ( 1 )。最初 ( 0 番目 ) の構造体の len ( 10 ) を 1 増やして 11 が出力されます。
(gdb) p ++p->len
$2 = 11
p コマンドで、(++p)->len
を表示させます ( 2 )。p が 1 増やされて、次 ( 1 番目 ) の構造体の len ( 20 ) が出力されます。
(gdb) p (++p)->len
$3 = 20
p コマンドで、(p++)->len
を表示させます ( 3 )。len ( 20 ) が出力された後に、p が 1 増やされます。
(gdb) p (p++)->len
$4 = 20
p コマンドで、現在の p->len
を表示させます。次 ( 2 番目 ) の構造体の len ( 30 ) が出力されます。
(gdb) p p->len
$5 = 30
p コマンドで、現在の *p->str
を表示させます ( 4 )。2 番目の構造体の str が指す g
( "ghi" の 0 番目 ) が出力されます。
(gdb) p *p->str
$6 = 103 'g'
p コマンドで、*p->str++
を表示させます ( 5 )。現在の str が指す g
が出力され、str が 1 増やされます。
(gdb) p *p->str++
$7 = 103 'g'
p コマンドで、現在の *p->str
を表示させます。現在の str が指す h
( "ghi" の 1 番目 ) が出力されます。
(gdb) p *p->str
$8 = 104 'h'
p コマンドで、コメントアウトしていた (*p->str)++
を表示させます。現在の str が指す h
( "ghi" の 1 番目 ) が出力された後、h
( 104 ) が 1 増やされます。
(gdb) p (*p->str)++
$9 = 104 'h'
p コマンドで、現在の *p->str
を表示させます。現在の str が指す 105 ( = 104 + 1 ) に相当する文字 i
が出力されます。
(gdb) p *p->str
$10 = 105 'i'
p コマンドで、*p++->str
を表示させます。現在の str が指す i
が出力された後、p が 1 増やされます。( 6 ) の結果とは異なります。
(gdb) p *p++->str
$11 = 105 'i'
p コマンドで、現在の *p->str
を表示させます。最後 ( 3 番目 ) の構造体の str が指す j
( "jkl" の 0 番目 ) が出力されます。
(gdb) p *p->str
$12 = 106 'j'
gdb を終了します。
(gdb) q