结构和联合
聚合数据类型(aggregate data type)能够同时存储超过一个的单独数据。C提供了两种类型的聚合数据类型:数组和结构。
数组:是相同类型的元素的集合,它的每个元素时通过下标引用或指针间接访问来选择的;
结构:也是一些值的集合,这些值成为它的成员member,但一个结构的各个成员可能具有不同的类型。
结构声明为:
struct tag {member-list} variable-list;
红色部分为可选部分,可选部分不能全部省略,至少要出现两个。
struct {
int a;
char b;
float c;
}x;
struct {
int a;
char b;
float c;
}y[10],*z;
如上面所示,即使两个结构的成员列表完全相同,但是编译器仍然会把它们当做完全不同的两种类型来对待。
直接访问通过点操作符.访问。点操作符结构两个操作数,左操作数就是结构变量的名字,右操作数就是需要访问的成员的名字。
对于结构
struct data {
int a;
char b;
float c;
};
那么struct data *x,我们应该使用(*x).a来访问a,但是这样写太繁琐。C语言提供了一种更方便的操作符,即箭头操作符。使用x->a即可访问a。
在一个结构内部包含一个类型为该结构本身的成员是非法的
,比如:
struct data {
int a;
char b;
float c;
struct data x;
};
其中的struct data会无限递归。我们可以通过包含一个该结构的指针来解决。
struct data {
int a;
char b;
float c;
struct data *x;
};
正确的原因是,编译器在结构的长度确定之前就已经知道了指针的长度,所以这种类型的自引用是合法的。
如果你觉得一个结构内部包含一个指向该结构本身的指针有些奇怪,请记住它事实上所指向的是同一种类型的不同结构,比如更高级的数据结构,链表和树,都是采用这种技巧实现的。
警惕陷阱
typedef struct {
int a;
char b;
float c;
DATA *x;
}DATA;
上面这个结构是失败的,因为类型名DATA直到声明的末尾才定义,所以在结构声明的内部它尚未定义。解决方法为定义一个结构标签:
typedef struct DATA_TAG {
int a;
char b;
float c;
struct DATA_TAG *x;
}DATA;
当两个结构互相引用时,肯定存在一些依赖的结构。此时应该首先声明哪一个呢?我们可以使用不完整声明(incomplete declaration)来解决这个问题,它声明一个作为结构标签的标识符。然后,我们可以把这个标签用在不需要知道这个结构的长度的声明中,如声明指向这个结构的指针。接下来的声明把这个标签与成员列表联系在一起。例如:
struct B;
struct A {
struct B *partner;
int a;
float b;
}
struct B {
struct A *partner;
int a;
float b;
}
比如对结构:
struct data {
int a;
char b;
float c;
struct data *x;
};
我们定义一个struct data m;
pa = &m;
pi = &(m.a);
我们可以知道pa和pi的值其实是相同的,因为pa是整个结构的地址,而pi是a的地址,而a是结构的第一个元素,所以值相同。但是两个是不等同的,因为pa是指向结构的指针,而pi是指向int的指针。可以通过强制类型转换pi = (int *)pa来令两者相等,但是有很大危险性。
当把结构作为参数产地给函数时,最好使用结构的指针,而不是结构本身。比如:
struct data {
int a;
char b;
float c;
struct data *x;
}DATA;
l void test(DATA x); —这种方法是不提倡的;复制DATA,如果DATA结构十分庞大,会占用很大的空间;
l void test(DATA *x); —这种方法是提倡的
,只是传递一个指针。
l void test(register DATA const*x); —这种方法是大力提倡的![clip_image002[1] clip_image002[1]](http://guoshaoguang.com/wp-content/uploads/2013/03/clip_image0021_thumb.png)
![clip_image002[2] clip_image002[2]](http://guoshaoguang.com/wp-content/uploads/2013/03/clip_image0022_thumb.png)
,这种模式可以提高速度,并且可以防止指针被修改。
什么时候应该向函数传递一个结构而不是指针呢?很少有这种情况。只有当一个结构特别的小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。但是对于绝大多数的结构,传递指针显然效率更高。如果你希望修改结构的任何成员,也应该使用指针传递方案。
位段的声明和结构类似,但它的成员时一个或多个位的字段,这些不同长度的字段实际上存储于一个或多个整型变量中。
位段的声明和任何普通的结构成员声明相同,但有两个例外:
l 首先,位段成员必须声明为int、signed int或unsigned int类型;
l 其次,在成员们的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。
Mark:用signed或unsigned整数显式地声明位段是个好主意,因为int在不同的机器上解释可能不同,指明了signed或unsigned类型有利于移植。
使用位段的好理由是,①位段能够把长度为奇数的数据包装在一起,节省存储空间;②使用位段还可以很方便地访问一个整型值的部分内容。
联合的所有成员引用的是内存中的相同位置。当你想在不同的时刻把不同的东西存储于同一个位置时,就可以使用联合。

上述联合在我的机器上共占用8个字节。
l 如果成员f被使用,这个字就作为浮点型访问;
l 如果成员i被使用,这个字就作为整型值访问;
l 如果成员d被使用,这个字就作为双精度浮点型访问;
☆如果联合的各个成员具有不同的长度,联合的长度就是它最长成员的长度。比如,上述的联合fid的联合长度就应该是double类型做占用的字节数。这样的话,联合就足以容纳它最大的成员,但是如果这些成员的长度相差悬殊,当存储长度较短的成员时,浪费的空间是相当可观的。在这种情况下,更好的方法是在联合中存储指向不同成员的指针而不是直接存储成员本身,这样就解决了内存浪费的问题,当它需要使用哪个成员时,就分配正确数量的内存来存储它。
联合可以被初始化,但这个①初始值必须是第一个成员的类型(如果被赋予其他值,有可能会被强制类型转换),②而且它必须位于一对花括号里面。例如:

l 不同的结构声明即使它们的成员列表相同也被认为是不同的类型;
l 结构不能包含类型也是这个结构的成员,但是它的成员可以是一个指向这个结构的指针,这个技巧长用于链式数据结构中;
l 为了声明两个结构,每个结构都包含一个指向对方的指针的成员,我们需要使用不完整的声明来定义一个结构标签名;
l 结构可以作为参数传递给函数,也可以作为返回值从函数返回。但是,向函数传递一个指向结构的指针往往效率更高。在结构指针参数的声明中可以加上const关键字防止函数修改指针所指向的结构。
l 向函数传递结构参数是低效的,最好传递指针。