聚合数据类型能够同时存储超过一个的单独数据。
C提供了两种类型的聚合数据类型——数组和结构。
数组是相同类型的元素的集合,它的每个元素都是通过下标引用或指针间接访问来选择的。 数组元素可以通过下标来访问,这只是因为数组的元素长度相同。结构也是一些值的集合,这些值称为它的成员。但一个结构的各个成员可能具有不同的类型。 由于结构中各个成员可能具有不同的类型,导致结构的成员可能长度不同,所以我们不能使用下标来访问它们。相反,每个结构成员都有自己的名字,它们是通过名字来访问的。首先,我要给出在结构声明中最重要的知识点:
在声明结构时,必须列出它包含的所有成员,这个列表包括每个成员的类型和名字。
接下来,我们一步一步的分析结构变量的声明方式以及它与其他数据类型变量的声明方式的不同之处。
对于其他数据类型变量,我们的声明一般如下所示:
数据类型变量名;inta;doubleb;charc;int*d;按照一般的思维,那我们对结构体变量的声明应该如下所示:
struct var;但是,我们前面说过——在声明结构时,必须列出它包含的所有成员,这个列表包括每个成员的类型和名字。
从上面的声明语句中,我们看不出结构变量var包含成员的信息。
所以,我们更新(改进)对结构体变量的声明:
struct { int a; double b; char c; int *d; } var;此时,我们可以通过声明语句知道,结构体变量var中包含四个成员变量——a,b,c,d以及它们的类型。
但是,这种声明方式存在一种缺点——不同的声明语句会导致结构体变量的类型不同!
struct { int a; char b; float c; } x; struct { int a; char b; float c; } y[20] , *z;第一个声明语句创建了结构变量x,它包含三个成员a,b,c。
第二个声明语句创建了结构变量y和z。其中,
y是一个数组,包含了20个结构。
z是一个指针,指向这个类型的结构。
但是,即使它们的成员列表完全相同,这两个声明语句声明的变量被编译器当作两种截然不同的类型。
即,变量y和z的类型和x的类型不同。
所以,下面语句将是错误的:
z = &x;所以,我们接着更新(改进)对结构体变量的声明。
为了保证包含相同成员的结构体声明的变量的类型相同,C 语言增加了标签字段tag。
标签字段tag被视为成员列表的一个等价名字。
标签字段tag允许多个声明使用同一个成员列表,并且创建同一种类型的结构变量。
例如,通过使用下列的语句,将标签字段simple等价于后面花括号中各个变量的声明。
struct simple{ int a ; char b; float c; };然后,我们可以使用simple代替成员列表,按照上述方法声明变量。
struct simple x; struct simple y[20] , *z;不同的是,变量x,y,z是同一类型的结构变量。
最后,最优的方法,也是推荐的方法是使用关键字typedef。
关键字typedef的作用是为一个数据类型定义一个新的名称。
例如,通过下面语句,我们将结构体类型定义为Simple。
typedef struct { int a; char b; float c; } simple;然后,我们可以使用simple去定义结构体变量:
simple x; simple y[20] , *z;在一个结构体内部包含一个类型为该结构本身的成员是否合法呢?
这个问题的核心在于——结构变量在声明时,编译器分配给它的内存空间的大小能否被确定!
通过下面两个例子,我们进行叙述:
struct SELF_REF1{ int a; int b; struct SELF_REF1 c; };这种方式的自引用声明是非法的!
这是因为,成员c是一个完整的结构,其内部还将包含它自己的成员c,这个c就是另外一个完整的结构,它还将包含它自己的成员c。这样重复下去永无止境,编译器无法确定分配给它的内存空间大小。
struct SELF_REF2{ int a; int b; struct SELF_REF2 *c; };这种方式的自引用声明是合法的!
指针变量的内存空间大小确定,编译器可以知道分配给它的内存空间大小。
如何声明一些相互之前存在依赖的结构,也就是说,其中一个结构包含了另外一个结构的一个或者多个成员呢?
如果每个结构都引用了其他结构的标签,哪个结构应该首先声明呢?
这个的问题的解决方案是使用不完整声明。
不完整声明的方式如下:
首先,声明一个作为结构标签的标识符。然后,我们可以把这个标签用在不需要知道这个结构的长度的声明中,例如声明指向这个结构的指针。最后,通过声明语句把这个标签与成员列表联系在一起。 struct B; struct A{ struct B *partner; }; struct B{ struct A *partner; };结构的初始化方式和数组的初始化方式很相似。一个位于一个花括号{}内部,由逗号分隔的初始值列表可用于结构各个成员的初始化。这些值根据结构成员列表的顺序写出。如果初始列表的值不够,剩余的结构成员将使用默认值进行初始化。
结构中如果包含数组或结构成员,其初始化方式类似于多维数组的初始化。一个完整的聚合类型成员的初始值列表可以嵌套与结构的初始值列表内部。
typedef struct { int a; char b; float c; } simple; struct INIT_EX{ int a; short b[10]; simple c; } x = { 10, {1,2,3,4,5}, {25,'x',1.9} };通过上述声明,我们创建了一个结构变量comp。
结构变量的成员是通过点操作符.直接访问的。
点操作符接受两个操作数:
左操作数就是结构变量的名字右操作数就是需要访问的成员的名字表达式的结果就是指定的成员。
例如:
我们通过表达式comp.a选择了数组a这个成员,表达式的结果是个数组名,所以我们可以把它用在任何可以使用表数组名的地方。
我们通过表达式comp.s选择了结构s这个成员,表达式的结果是个结构名,所以我们可以把它用在任何可以使用结构名的地方。所以,我们可以通过表达式comp.s.a选择结构s的成员a。
那么,comp.sa[4].c这个表达式的结果是什么呢?
按照上述直接访问的描述,如果我们拥有一个指向结构的指针,我们一般按照如下的方法访问结构的成员:
首先,对指针执行间接访问操作,获得此结构。然后,使用点操作符来访问它的成员。最后,由于点操作符的优先级高于解引用操作符的优先级,我们必须在表达式中使用括号,确保间接访问首先执行。所以,最后的表达式如下:
(*cp).f由于我们很容易漏掉括号而导致错误,所以C语言通过提供了一种新的操作符来确保我们书写的正确,那就是箭头操作符->。
同样,箭头操作符也接受两个操作数,左操作数必须是一个指向结构的指针。箭头操作符对左操作数执行间接访问取得指针所指向的结构,然后和点操作符一样,根据右操作数选择一个指定的结构成员。
那么,上述表达式可以写成:
cp -> f;