英文原版:P377
这一章介绍3种新的类型:结构体structures、联合体unions、枚举enumerations。
在这3个类型中,结构体是最重要的。
- 结构体是多个不同类型的数值的集合。
- 联合体跟结构体类似,也是多个数据类型的数值的集合。但联合体的字段共用同样的存储。因此,联合体一次只能存储一个字段,不能同时存储所有的字段。
- 枚举是一个整数类型,枚举的值可由程序员来命名。
本章主要内容:
- 16.1节介绍如何声明结构体变量,如何对结构体变量执行基本操作;
- 16.2节介绍如何定义结构体类型,支持编写接受结构体类型参数或者返回值是结构体类型的函数;
- 16.3节介绍如何嵌套使用结构体和数组;
- 16.4节介绍联合体类型;
- 16.5节介绍枚举类型;
16.1节 结构体变量
- 如何声明结构体变量?
- 如何初始化结构体变量?
- 如何对结构体变量执行基本操作?
数组和结构体的比较
数组
- 数组的所有元素都有相同的类型;
- 通过下标来获取数组元素;
结构体
- 结构体的所有成员的类型不一定相同;
- 结构体的每个成员都有名字,通过名字来获取结构体的成员,而不是位置;
16.1.1 声明结构体变量
- 如何声明?
- 内存中如何存储?
- 作用域
假设要记录一个仓库里的零件信息:零件编号(整数类型)、零件名(字符串)、当前数量(整数类型)等,可做如下的结构体声明:
#define NAME_LEN 25
struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} part1, part2;
- 每个结构体变量都有3个成员:
number
、name
、on_hand
; -
struct {...}
描述了结构体类型,part1
、part2
是结构体类型的变量;
结构体的成员是按照其声明的顺序依次在内存中存储的。
-
part1
的存储起始位置是2000; -
number
字段占据4个字节,存储范围是2000-2003; -
name
字段占据25个字节,存储范围是2004-2029; -
on_hand
占据4个字节,存储范围是2030-2033;
每个结构体有单独的命名空间,也就是说每个结构体表示一个新的作用域。在每个结构体作用域里声明的任何名字不会跟程序中的其他名字冲突。
比如
#define NAME_LEN 25
struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} part1, part2;
struct {
char name[NAME_LEN+1];
int number;
char sex;
} employee1, employee2;
16.1.2小节 结构体变量的初始化
情形一:在声明的同时使用结构体初始化式进行初始化
#define NAME_LEN 25
struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} part1 = {528, "Disk drive", 10},
part2 = {914, "Printer cable", 5};
-
part1
的初始化式为{528, "Disk drive", 10}
; -
part2
的初始化式为{914, "Printer cable", 5}
结构体初始化式遵循的规则:
- 在初始化式里使用的表达式必须是常量,不能使用变量来给
part1
的on_hand
字段初始化。 - 结构体初始化式里的成员个数可以比其要初始化的结构体少;
- 任何被省略的成员的初始值都是0;如果被省略的是一个字符数组,则该字符数组里的字节都是0,表示一个空字符串;
- 初始化式只能出现在声明中,而复合字面量可以用来给结构体变量赋值;
在C99里的指定初始化式
#define NAME_LEN 25
struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} part1 = {.num = 528, .name = "Disk drive", .on_hand = 10},
part2 = {.num = 914, .name = "Printer cable", .on_hand = 5};
-
part1
的指定初始化式为{.num = 528, .name = "Disk drive", .on_hand = 10}
; -
part2
的指定初始化式为{.num = 914, .name = "Printer cable", .on_hand = 5}
;
注意:
- 在指定初始化式中不是所有的值都有指示符前缀;
比如,{.number = 528, "Disk drive", .on_hand = 10}
,编译器会默认"Disk drive"
是给紧接着number
后的字段初始化的。 - 任何初始化式没有涉及到的成员的值都被设为0;
16.1.3小节 结构体的操作
- 对结构体成员的操作
读操作,即对结构体成员的访问;
写操作,比如给结构体成员赋值,对结构体成员进行自增或者自减操作等; - 对整个结构体的操作
只有赋值操作;
注意:
-
不能使用运算符
==
和!=
来判断两个结构体相等或者不等;
//ch16_1.c
#include<stdio.h>
#define NAME_LEN 25
int main()
{
struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} part1 = {528, "Disk drive", 10}, part2;
// part2 = {914, "Printer cable", 5};
// 访问结构体变量
printf("Part1 number: %d\n", part1.number);
printf("Part1 name: %s\n", part1.name);
printf("Part1 Quantity on hand: %d\n", part1.on_hand);
// 对结构体成员的操作,比如赋值、自增、自减等
part1.number = 258;
part1.on_hand++;
printf("Part1 number: %d\n", part1.number);
printf("Part1 Quantity on hand: %d\n", part1.on_hand);
// 从键盘上读取一个值给part1.on_hand
scanf("%d", &part1.on_hand);
printf("Part1 Quantity on hand: %d\n", part1.on_hand);
// 整个结构体的赋值操作
part2 = part1;
printf("Part2 number: %d\n", part2.number);
printf("Part2 name: %s\n", part2.name);
printf("Part2 Quantity on hand: %d\n", part2.on_hand);
return 0;
}
16.2 结构体类型
- 声明结构体标记
- 声明定义结构体类型
- 结构体作为函数参数和函数返回值
- 复合字面量
16.2.1小节 声明结构体标记
//ch16_2.c
#include<stdio.h>
#define NAME_LEN 25
int main()
{
// struct {
// int number;
// char name[NAME_LEN+1];
// int on_hand;
// } part1 = {528, "Disk drive", 10};
// printf("Part1 number: %d\n", part1.number);
// printf("Part1 name: %s\n", part1.name);
// printf("Part1 Quantity on hand: %d\n", part1.on_hand);
// struct {
// int number;
// char name[NAME_LEN+1];
// int on_hand;
// } part2;
//注意,根据C语言规范,part1和part2的类型是不兼容的。
// part2 = part1;
// printf("Part2 number: %d\n", part2.number);
// printf("Part2 name: %s\n", part2.name);
// printf("Part2 Quantity on hand: %d\n", part2.on_hand);
struct part {
int number;
char name[NAME_LEN+1];
int on_hand;
};
//注意,不能省略struct,part表示一个类型名,只是一个标记名
// struct part part1, part2;
//混合使用结构体标记声明和结构体变量声明
// struct part {
// int number;
// char name[NAME_LEN+1];
// int on_hand;
// } part1, part2;
//所有声明为struct part类型的结构体类型是兼容的,可以进行赋值操作
struct part part1 = {528, "Disk drive", 10};
struct part part2;
part2 = part1;
printf("Part1 number: %d\n", part1.number);
printf("Part1 name: %s\n", part1.name);
printf("Part1 Quantity on hand: %d\n", part1.on_hand);
printf("Part2 number: %d\n", part2.number);
printf("Part2 name: %s\n", part2.name);
printf("Part2 Quantity on hand: %d\n", part2.on_hand);
return 0;
}
- 该示例声明了一个名为
part
的结构体标记; - 右大括号
}
后面的分号;
表示声明的结束,不能省略;
16.2.2小节 定义结构体类型
//ch16_2_2.c
#include<stdio.h>
#define NAME_LEN 25
//使用typedef来定义结构体类型
//注意类型的名字Part必须出现在结尾,而不是在struct后面
//由于Part是一个typedef名,则不允许出现struct Part
//所有的Part类型变量都是兼容的
typedef struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} Part;
int main()
{
// Part part1, part2;
Part part1 = {528, "Disk drive", 10};
Part part2;
part2 = part1;
printf("Part1 number: %d\n", part1.number);
printf("Part1 name: %s\n", part1.name);
printf("Part1 Quantity on hand: %d\n", part1.on_hand);
printf("Part2 number: %d\n", part2.number);
printf("Part2 name: %s\n", part2.name);
printf("Part2 Quantity on hand: %d\n", part2.on_hand);
return 0;
}
注意
- 类型的名字
Part
必须出现在结尾,而不是在struct
后面 - 由于
Part
是一个typedef
名,则不允许出现struct Part
- 所有的
Part
类型变量都是兼容的
16.2.3小节 结构体作为函数参数和返回值
//ch16_2_3.c
#include<stdio.h>
#include<string.h>
#define NAME_LEN 25
struct part {
int number;
char name[NAME_LEN+1];
int on_hand;
};
void print_part(struct part);
struct part build_part(int number, const char *name, int on_hand);
int main()
{
struct part part1 = {528, "Disk drive", 10};
print_part(part1);
struct part part2 = build_part(120, "Disney", 12);
print_part(part2);
return 0;
}
void print_part(struct part p){
printf("Part number: %d\n", p.number);
printf("Part name: %s\n", p.name);
printf("Part Quantity on hand: %d\n", p.on_hand);
}
struct part build_part(int number, const char *name, int on_hand)
{
struct part p;
p.number = number;
strcpy(p.name, name);
p.on_hand = on_hand;
return p;
}
性能分析:
- 给函数传递结构体参数,以及从函数返回一个结构体都需要生成结构体的所有成员的拷贝。
- 如果结构体很大时,会有一定的系统开销。
- 为了避免这类开销,常见的做法是传递给函数一个指向结构体的指针,以及从函数中返回一个指向结构体的指针。
16.2.4小节 复合结构体字面量
什么是复合结构体字面量?
(类型) {成员1的值, 成员2的值,...}
复合结构体字面量有哪些用途?
print_part((struct part){528, "Disk drive", 10});
part1 = (struct part){528, "Disk drive", 10};
print_part((struct part){.on_hand = 10, .name = "Disk drive", .number = 528});
- 创建结构体,作为参数传递给函数
- 给结构体变量赋值
16.3节 嵌套使用数组和结构体
- 在结构体内嵌套数组
- 结构体的成员是结构体
- 结构体数组
结构体嵌套结构体
//ch16_3.c
#include<stdio.h>
#include<string.h>
#define FIRST_NAME_LEN 25
#define LAST_NAME_LEN 25
//结构体标记和结构体类型声明
struct person_name {
char first[FIRST_NAME_LEN+1];
char middle_initial;
char last[LAST_NAME_LEN+1];
};
struct student {
struct person_name name;
int id, age;
char sex;
} student1, student2;
void display_name(struct person_name name);
int main()
{
strcpy(student1.name.first, "Liyang");
student1.name.middle_initial = '_';
strcpy(student1.name.last, "Hao");
display_name(student1.name);
struct person_name new_name;
strcpy(new_name.first, "Leiming");
new_name.middle_initial = '_';
strcpy(new_name.last, "Hao");
student1.name = new_name;
display_name(student1.name);
return 0;
}
void display_name(struct person_name name){
printf("Person name first: : %s\n", name.first);
printf("Person name middle: %c\n", name.middle_initial);
printf("Person name first: %s\n", name.last);
}
16.3.2小节 结构体数组
//ch16_3_2.c
#include<stdio.h>
#include<string.h>
#define NAME_LEN 25
struct part {
int number;
char name[NAME_LEN+1];
int on_hand;
};
void print_part(struct part);
int main()
{
//声明结构体数组
struct part inventory[100];
int i = 3;
//访问结构体数组中的某个元素
print_part(inventory[i]);
//访问结构体数组中的某个元素的成员
inventory[i].number = 883;
//将存储在第i个位置的零件的名字置空
inventory[i].name[0] = '\0';
return 0;
}
void print_part(struct part p){
printf("Part number: %d\n", p.number);
printf("Part name: %s\n", p.name);
printf("Part Quantity on hand: %d\n", p.on_hand);
}
- 结构体数组声明
- 访问结构体数组中的某个元素
- 访问结构体数组中的某个元素的成员
- 结构体数组的初始化
16.3.3小节 初始化一个结构体数组
应用场景:
- 初始化一组在程序执行期间不会改变的数据库信息
//ch16_3_3.c
//结构体标记
struct dialing_code {
char *country;
int code;
};
int main()
{
const struct dialing_code country_codes[] =
{
{"Argetina", 84}, {"Bangladesh", 880},
{"Brazil", 55}, {"China", 86}
};
return 0;
}
16.4节 联合体unions
- 声明联合体类型变量
- 存储联合体类型变量
- 读写联合体类型变量的成员
- 声明联合体类型标记
- 定义联合体类型
- 联合体类型有哪些使用场景?
union {
int i;
double d;
} u;
- 该示例声明了一个有两个成员的联合体变量u;
- 结构体变量s的成员存储在内存中的不同地址,联合体变量u的成员存储在同一地址;
- 在结构体变量s中,成员i和d占据着内存的不同位置,总大小为12个字节;
- 在联合体变量u中,成员i和成员d的内存地址是相同的,总大小为8个字节;
读写联合体变量
union {
int i;
double d;
} u;
u.i = 82;
u.d = 74.8;
- 该示例揭示了编译器是如何存储联合体类型的成员的。
- 因为编译器是重叠存储一个联合体类型的成员的,所以修改一个成员的值会改变任何一个成员之前存储的值。比如u.i的值就丢失了。
- 联合体变量u是一个存储i或者d的地方,而不是i和d的地方。
联合类类型的使用场景有哪些?
- 使用赋值运算来拷贝;
- 用作函数参数;
- 用作函数返回值;
- 可使用初始化式来初始化;
- 使用联合体来节省空间;
网友评论