数据经常以成组的形成存在,在C中,使用结构可以把不同类型的值存储在一起。
1.结构基础知识
聚合数据类型能够同时存储超过一个的单独数据,C提供了两种类型的聚合类型,数组和结构。数组是相同类型的元素的集合;结构也是一些值的集合,这些值称为它的成员,但一个结构的各个成员可能具有不同的类型。结构变量属于标量类型。
1)结构的声明
struct {
int a;
char b;
float c;
}x;
struct {
int a;
char b;
float c;
}y[20],*z;
struct SIMPLE {
int a;
char b;
float c;
};
struct SIMPLE x;
struct SIMPLE y[20],*z;
typedef struct {
int a;
char b;
float c;
}Simple;
Simple x;
Simple y[20],*z;
2)结构成员
struct COMPLEX {
float f;
int a[20];
long *lp;
struct SIMPLE s;
struct SIMPLE sa[10];
struct SIMPLE *sp;
};
一个结构的成员名字可以和其它结构的成员的名字相同,所以这个结构的成员a并不会与struct SIMPLE s的成员a冲突,正如看到的一样,成员的访问方式允许你指定任何一个成员而不至于产生歧义。
3)结构成员的直接访问
struct COMPLEX comp;
((comp.sa)[4]).c <==> comp.sa[4].c
4)结构成员的间接访问
void func(struct COMPLEX *cp)
函数可以通过(*cp).f访问成员f;
c语言为了更方便完成访问,提出了箭头操作符,和点操作符一样,箭头操作符接受两个操作数,但左操作数必须是一个指向结构的指针
cp->f;
cp->a;
cp->s;
5)结构的自引用
struct SELF_REF1{
int a;
struct SELF_REF1 b;
int c;
};
//这种自引用是非法的,因为会永无止境的重复下去
struct SELF_REF2{
int a;
struct SELF_REF2 *b;
int c;
};
//这样自引用才是合法的,广泛用于链表和树
typedef struct{
int a;
SELF_REF3 *b;
int c;
}SELF_REF3;
//这样创建是失败的,类型名直到声明的末尾定义,所以在结构声明的内部它尚未定义
解决方案是定义一个结构标签来声明b:
typedef struct SELF_REF3_TAG{
int a;
struct SELF_REF3_TAG *b;
int c;
}SELF_REF3;
6)不完整的声明
struct B;
struct A{
struct B *partner;
/*other declarations*/
};
struct B{
struct A *partner;
/*other declarations*/
};
//在A成员列表中国需要标签B的不完整声明,一旦A被声明之后,B的成员列表也可以被声明;
7)结构的初始化
struct INT_EX{
int a;
short b[10];
Simple c;
}x={
10,
{1,2,3,4,5},
{25,'x',1.9}
};
2.结构、指针和成员
1)访问指针
2)访问结构
3)访问结构成员
4)访问嵌套结构
5)访问指针成员
3.结构的存储分配
编译器按照成员列表的顺序一个接一个地给每个成员分配内存,只有当存储成员时需要满足正确的边界对齐要求时,成员之间才可能出现用于填充的额外内存空间。
系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐要求,因此所有结果的起始存储位置必须是结构中边界要求最严格的数据类型的要求的位置。对结构的成员列表重新排列,让那些对边界要求严格的成员首先出现,对边界要求最弱的成员最后出现,这种做法可以最大限度地减少因边界对齐而带来的空间损失。
sizeof操作符能够得出一个结构的整体长度,包括因边界对齐而跳过的那些字节。如果开发者必须确定结构某个成员的实际位置,应该考虑边界对齐因素,可以使用offsetof宏(定义与stddef.h):offsetof(type,member)。
struct ALIGN{
char a;
int b;
char c;
};
offsetof(struct ALIGN,b)的返回值是4
4.作为函数参数的结构
结构变量是一个标量,它可以用于其他标量可以使用的任何场所。什么时候你应该向函数一个结构而不是一个指向结构的指针呢?很少有这种情况。只有当一个结构特别的小(长度的指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案,但对于绝大对数结构,传递指针显然效率更高。
5.位段
struct CHAR{
unsigned ch :7;
unsigned font :7;
unsigned size :7;
};
struct CHAR ch1;
6.联合
联合的声明和结构类似,但它的行为方式却和结构不同,联合的所有成员引用的是内存中的相同位置。当开发者想要在不同时刻把不同的东西存储于同一个位置时,就可以使用联合。分配给联合的内存数量取决于它的最长成员的长度。
union{
float f;
int i;
}fi;
//联合变量可以被初始化,但这个初始值必须联合第1个成员的类型
uion{
int a;
float b;
char c[4];
}x={5};
把x.a初始化为5.
7.总结
结构的声明列出了结构包含的成员列表。结构的成员可以是标量、数组或指针。结构不能包含类型也是这个结构的成员,但它的成员可以是一个指向这个结构的指针。编译器为一个结构变量的成员分配内存时要满足它们的边界对齐要求。结构可以作为参数传递给函数,也可以作为返回值从函数返回。位段是结构的一种,但它的成员长度以位为单位指定。一个联合的所有成员都存储于同一个内存位置,通过访问不同类型的联合成员,内存中相同的位组合可以被解释为不同的东西。
8.警告的总结
1)具有相同成员列表的结构声明产生不同类型的变量;
2)使用typedef为一个自引用的结构定义名字时应该小心;
3)向函数传递结构参数是低效的;
9.编程提示总结
1)把结构标签声明和结构的typedef声明放在头文件中,当源文件需要这些声明时可以通过#include指令把它们包含进来;
2)结构成员的最佳排列形式并不一定就是考虑边界对齐而浪费内存空间最少的那种排列形式;
3)把位段成员显式地声明为singed int或unsigned int类型;
4)位段是不可移植的;
5)位段使源代码中位的操作表达得更为清楚;