第十章 结构体、共用体、枚举类型
10.1 结构体类型与结构体变量
10.1.1 结构体类型的定义
在C语言中,结构体类型即是一种构造而成的数据类型。那么在声明和引用之前必须先定义再使用。
结构体类型的定义格式一般为:
struct 结构体名
{
成员列表;
};
其中,struct是关键字,结构体名是用户指定的结构体类型名称,每一个结构体成员也需要指明其类型和名称。定义结构体类型和变量一样,也是语句,结尾要加分号。结构体名和成员名的命名应符合标识符的命名规定。
10.1.2 结构体变量的定义
结构体类型定义后,才可以定义该类型的变量。结构体变量所占的内存空间至少是所有成员变量空间之和。系统为结构体变量的各个成员依照定义顺序依次分配内存空间。
1.先定义结构体类型再定义结构体变量
struct SIMPLE { char *firstName; char *lastName; char *title; unsigned int age; }; //这个声明把标签SIMPLE和这个成员列表联系在一起,该声明并没有提供变量列表,所以它并未创建任何变量。 struct SIMPLE x; struct SIMPLE y[20], *z;
这些声明使用标签来创建变量,现在x,y,z都是同一种类型的结构体变量。
2.在定义结构体类型的同时定义结构体变量
struct SIMPLE { char *firstName; char *lastName; char *title; unsigned int age; }x,y[20],*z;
3.直接定义结构体变量省去结构体的名称。
struct { char *firstName; char *lastName; char *title; unsigned int age; }x; struct { char *firstName; char *lastName; char *title; unsigned int age; }y[10],*z;
这两种声明被编译器当作两种截然不同的声明,即使它们成员列表完全相同,因此变量x的类型和y、z的类型不同。所以:
z = &x 是非法的。
4.用typedef关键字简化
typedef struct { char *firstName; char *lastName; char *title; unsigned int age; }Simple;
Simple是个类型名而不是结构标签,所以后续的声明可以像下面这样:
Simple x; Simple y[10], *z;
也可以声明一个Simple指针并为它分配内存,如下所示:
Simple *pz; pz = (Simple *)malloc(sizeof(Simple));
结构变量的成员通过点操作符访问,点操作符接受两个操作数,左操作数就是结构变量的名字,右操作数是需要访问成员的名字,这个表达式的结果就是指定的成员。用点表示法来访问其字段,如下所示:
Simple x; x.firstName = (char *)malloc(strlen("Amy" + 1)); strcpy(x.firstName, "Amy"); x.age = 28;
箭头操作符接受两个操作数,但左操作数必须是一个指向结构体的指针。箭头操作符对左操作数执行间接访问取得指针所指向的结构,然后和点操作符一样,根据右操作数选择一个指定的成员。如下所示:
Simple *pz; pz = (Simple *)malloc(sizeof(Simple)); pz->firstName = (char *)malloc(strlen("Amy" + 1)); strcpy(pz->firstName, "Amy"); pz->age = 28;
不一定非要用箭头操作符,可以先解引用指针然后用点操作符,如下所示:
Simple *pz; pz = (Simple *)malloc(sizeof(Simple)); (*pz).firstName = (char *)malloc(strlen("Amy" + 1)); strcpy((*pz).firstName, "Amy"); (*pz).age = 28;
点操作符的优先级高于间接访问操作符,必须在表达式中使用括号。此种方法笨拙,不建议使用。
在为结构体分配内存时,运行时系统不会自动为结构体内部的指针分配内存。类似地,当结构体消失时,运行时系统也不会自动释放结构体内部的指针指向的内存。
结构的自引用
struct Self_Ref1 { int a; struct Self_Ref b; int c; };
这种类型的自引用是非法的,因为成员b是另一个完整的结构,其内部还将包含它自己的成员b。有点像无休止的递归函数。但下面这个声明是合法的:
struct Self_Ref2 { int a; struct Self_Ref2 *b; int c; };
此时b是一个指针而不是一个结构。编译器在结构的长度确定之前就已经知道结构的长度,所以这种类型的自引用是合法的。一个结构内部包含一个指向该结构本身的指针,这指针指向的是同一种类型的不同结构。
警惕下面这个陷阱:
typedef struct { int a; Self_Ref3 *b;//struct 加在前面也行 int c; }Self_Ref3;
这个声明的目的是为这个结构创建类型名Self_Ref3。但是,它失败了,类型名直到声明的末尾才定义。所以在结构声明的内部它未定义。
解决方案是定义一个结构标签来声明b,如下所示:
typedef struct Self_Ref3_Tag { int a; struct Self_Ref3_Tag *b; int c; }Self_Ref3;
10.1.3 结构体变量的初始化
与其它类型一样,对结构体变量在定义的同时也可以初始化。这时将初值用一对大括号括起来,依次列出每个成员的值,所列成员值的个数可以少于成员个数,默认用0填充。例如:
struct stu SIMPLE = { "Amy","Nikita","Agent",32 };
结构体变量定义后不能再整体赋值,例如:
struct stu SIMPLE; SIMPLE = { "Amy","Nikita","Agent",32 };
两个类型一致的结构体变量可以用一个为另一个作初始化(称为复制初始化),例如:
C语言里会报错,但在C++中即.cpp里是合法的。
struct stu x = { "Amy","Nikita","Agent" , 32 }, y = x;
或
struct stu x = { "Amy","Nikita","Agent" , 32 }, y; y = x;
结构体中如果包含数组或结构体成员,其初始化方式类似于多维数组的初始化。一个完整的聚合类型成员的初始值列表可以嵌套于结构的初始值列表内部。
typedef struct { char *firstName; char *lastName; char *title; unsigned int age; }Simple; struct Complex { int a; short b[2][3]; Simple c; }x = { 10, {{1,2,3},{4,5,6}}, {"Amy","Nikita","Agent",32 }
};
10.1.4 结构体数组
数组的元素也可以是结构体类型的,因此可以定义结构体数组。结构体数组的每一个元素都是具有相同结构体类型的下标结构体变量。对结构体数组可以在定义时作初始化。例如:
struct stu{ char *firstName; char *lastName; char *title; unsigned int age; }Stu[2] = { {"Amy","Nikita","Agent",32}, {"Tom","Frank","Software",27} };
也可以先定义类型再定义结构体数组,例如:
struct stu{ char *firstName; char *lastName; char *title; unsigned int age; }; void main() { struct stu Stu[2] = { { "Amy","Nikita","Agent",32 }, { "Tom","Frank","Software",27 } }; }
10.1.5 指向结构体数组的指针
一个结构体变量的指针就是该变量所占据的内存段的起始地址。可以设置一个结构体类型的指针变量,来指向一个结构体变量,此时该指针变量的值就是结构体变量的起始地址,当然也可以用这种指针变量指向结构体数组或结构体数组的元素。
typedef struct { char product[10]; int quantity; float unit_price; float total_amount; }Transaction; void print_receipt(Transaction *trans) { printf("%s\n", trans->product); printf("%d @ %.2f total %.2f\n", trans->quantity, trans->unit_price, trans->total_amount); }
这个函数可以像下面这样调用:
Transaction current_trans;
print_receipt(¤t_trans);
10.2 共用体
有时需要让几种不同类型的变量存放到同一段内存单元中。这就需要覆盖技术,几个变量互相覆盖,并且每个成员分时段有效。这种让几个不同的变量占用同一段内存的构造,称为“共用体”。
10.2.1 共用体类型的定义
union 共用体名
{
成员列表;
};
其中union是关键字,共用体名是用户指定的共用体名称,每一个共用体成员需要指明其类型和名称。定义共用体类型和定义变量一样,也是语句,结尾要加分号。
共用体类型定义后即可进行共用体变量定义。共用体变量所占内存长度等于最长的成员的长度,且在某时段只有一个成员有效。
共用体变量中起作用的成员是最后一次存放的成员,存入一个新成员后原有的成员就失去作用。
不能再定义共用体变量时对它初始化,因为不知道那个在当时确定共用体中当前有效成员是谁。
共用体变量可以被初始化,但这个初始值必须是共用体第一个成员的的类型,而且它必须位于一对花括号里面。
union { int a; float b; char c[4]; }x = { 6 };
我们不能把这个类量初始化为一个浮点值或字符值。如果给出的初始值是其它任何类型,它就会转换为一个整数并赋值给x.a。
10.3 枚举类型
10.3.1 枚举类型的定义
一个变量如果仅有几种可能的整数值,而且每一个值都有确定的意义,就可以定义为枚举类型。枚举类型包含一组标识符,每一个标识符对应一个整型值。
例如,一周分为7天,可以按如下格式定义枚举类型:
enum day{ sun,mon,tue,wed,thu,fri,sat};
枚举类型的定义一般格式为:
enum 枚举类型名{枚举常量列表……};
注意:
1)枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
2)枚举常量列表描述了这类数据允许的取值范围,它们是整型常量,一般情况下系统规定依次为0,1,2,3……。
3)在定义枚举类型时可对枚举常量列表进行初始化以改变它们的值。
4)枚举常量可用于给枚举变量赋值,但枚举变量不能接受一个非枚举常量的值。
5)可以将一个整数强制类型转换后赋给枚举变量。