6.2 结构与函数
结构的合法操作只有几种:作为一个整体复制和赋值,通过 &
运算符取地址,访问其成员
其中,复制和赋值包括向函数传递参数以及从函数返回值
结构之间不可以进行比较
可以用一个常量成员值列表初始化结构,自动结构也可以通过赋值进行初始化
为了更进一步地理解结构,我们编写几个对点和矩形进行操作的函数
至少可以通过 3 种可能的方法传递结构:一是分别传递各个结构成员,二是传递整个结构,三是传递指向结构的指针
这 3 种方法各有利弊
makepoint
函数带有两个整型参数,并返回一个 point
类型的结构:
/* makepoint: make a point from x and y components */
struct point makepoint(int x, int y)
{
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}
注意,参数名和结构成员同名不会引起冲突,使用重名可以强调两者之间的关系
现在可以使用 makepoint
函数动态地初始化任意结构:
struct rect screen;
struct point middle;
struct point makepoint(int, int);
screen.pt1 = makepoint(0, 0);
screen.pt2 = makepoint(XMAX, YMAX);
middle = makepoint((screen.pt1.x + screen.pt2.x)/2, (screen.pt1.y + screen.pt2.y)/2);
接下来需要编写一系列的函数对点执行算术运算:
/* addpoints: add two points */
struct addpoint(struct point p1, struct point p2)
{
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
其中,函数的参数和返回值都是结构类型
之所以直接将相加所得的结果赋值给 p1
,而没有使用显式的临时变量存储
是为了强调结构类型的参数和其它类型的参数一样,都是通过值传递的
函数 prinrect
判断一个点是否在给定的矩形内部
我们采用这样一个约定:矩形包括其左侧边和底边,但不包括顶边和右侧边
/* ptinrect: return 1 if p in r, 0 if not */
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;
}
这里假设矩形是用标准形式表示的,其中 pt1
的坐标小于 pt2
的坐标
下列函数将返回一个规范形式的矩形:
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
/* canonrect: canonicalize coordinates of rectangle */
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;
}
如果传递给函数的结构很大,使用指针方式的效率通常比复制整个结构的效率要高
结构指针类似于普通变量指针,声明 struct point *pp;
将 pp
定义为一个指向 struct point
类型对象的指针
如果 pp
指向一个 point
结构,那么 *pp
即为该结构,而 (*pp).x 和(*pp).y
则是结构成员
可以按照下例中的方式使用 pp
:
struct point origin, *pp;
pp = &origin;
printf("origin is (%d,%d)\n", (*pp).x, (*pp).y);
其中 (*pp).x
中的圆括号是必需的,因为结构成员运算符 .
的优先级比 *
的优先级高
表达式 *pp.x
的含义等价于 *(pp.x)
,因为 x
不是指针,所以该表达式是非法的
结构指针的使用频度非常高,为了使用方便,C 语言提供了另一种简写方式
假定 p
是一个指向结构的指针,可以用 p->结构成员
的形式引用相应的结构成员
这样,就可以用 printf("origin is (%d,%d)\n", pp->x, pp->y);
的形式改写上面的代码
运算符 .
和 ->
都是从左至右结合的
所以,对于声明 struct rect r, *rp = &r;
以下 4 个表达式是等价的:
r.pt1.x
rp->pt1.x
(r.pt1).x
(rp->pt1).x
在所有运算符中,结构运算符 .
和 ->
、用于函数调用的 ()
以及用于下标的 []
的优先级最高
因此,它们同操作数之间的结合也最紧密
例如,对于结构声明
struct {
int len;
char *str;
} *p;
表达式 ++p->len
将增加 len
的值,而不是增加 p
的值,因为其中的隐含括号关系是 ++(p->len)
可以使用括号改变结合次序,如 (++p)->len
将先执行 p
加 1
,再对 len
执行操作
而 (p++)->len
则先对 len
执行操作,然后再将 p
加 1
(该表达式中的括号可以省略)
同样的道理 *p->str
读取的是指针 str
所指向的对象的值
*p->str++
先读取指针 str
指向的对象的值,然后再将 str
加 1
(与 *s++
相同)
(*p->str)++
将指针 str
指向的对象的值加 1
*p++->str
先读取指针 str
指向的对象的值,然后再将 p
加 1