第9章 结构体与联合体
1. 结构体的深入理解
1.1 结构体的基本概念
结构体是一种复合数据类型,它可以包含不同类型的成员变量,这些成员变量被组织在一起,形成一个有意义的数据单元。
1.1.1 结构体的内存布局
结构体的成员在内存中是连续存储的,每个成员的内存地址都按照其声明的顺序依次排列。编译器会根据成员的类型和对齐要求来分配内存。
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct Point { int x; int y; };
printf("结构体大小:%zu 字节\n", sizeof(struct Point));
|
1.2 结构体的声明
1.2.1 基本声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct struct_name { member_type1 member_name1; member_type2 member_name2; };
struct Point { int x; int y; };
struct Student { char name[50]; int age; float score; };
|
1.2.2 匿名结构体
可以声明没有名称的结构体,称为匿名结构体。
1 2 3 4 5 6 7 8 9 10 11
| struct { int x; int y; } p1, p2;
p1.x = 10; p1.y = 20; p2 = p1;
|
1.2.3 嵌套结构体
结构体可以嵌套在其他结构体中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| struct Date { int year; int month; int day; };
struct Person { char name[50]; int age; struct Date birthday; };
struct Person person; person.name[0] = 'A'; person.name[1] = 'l'; person.name[2] = 'i'; person.name[3] = 'c'; person.name[4] = 'e'; person.name[5] = '\0'; person.age = 18; person.birthday.year = 2005; person.birthday.month = 5; person.birthday.day = 15;
|
1.3 结构体变量的定义和初始化
1.3.1 基本初始化
1 2 3 4 5 6 7
| struct Point p1; struct Student s1;
struct Point p2 = {10, 20}; struct Student s2 = {"Alice", 18, 95.5};
|
1.3.2 指定成员初始化(C99 及以上)
可以使用 .member 语法来指定初始化特定成员,其他成员会被初始化为 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct Point p3 = {.x = 5, .y = 15}; struct Student s3 = {.name = "Bob", .score = 88.5, .age = 19};
struct Person person = { .name = "Charlie", .age = 20, .birthday = { .year = 2003, .month = 10, .day = 20 } };
|
1.3.3 结构体的聚合初始化
可以使用花括号 {} 来初始化结构体数组或嵌套结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct Point points[] = { {10, 20}, {30, 40}, {50, 60} };
struct Point points[] = { [0] = {.x = 10, .y = 20}, [1] = {.x = 30, .y = 40}, [2] = {.x = 50, .y = 60} };
|
1.4 结构体成员的访问
1.4.1 使用点运算符访问
使用点运算符 . 访问结构体变量的成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| struct Point p; p.x = 10; p.y = 20; printf("点的坐标:(%d, %d)\n", p.x, p.y);
struct Circle { struct Point center; float radius; };
struct Circle c; c.center.x = 0; c.center.y = 0; c.radius = 5.0; printf("圆心:(%d, %d), 半径:%.2f\n", c.center.x, c.center.y, c.radius);
|
1.4.2 使用箭头运算符访问
使用箭头运算符 -> 访问结构体指针的成员。
1 2 3 4 5 6 7 8 9 10 11
| struct Point p = {10, 20}; struct Point *pp = &p;
printf("点的坐标:(%d, %d)\n", pp->x, pp->y);
pp->x = 30; pp->y = 40; printf("修改后:(%d, %d)\n", p.x, p.y);
|
1.5 结构体的赋值和比较
1.5.1 结构体赋值
结构体变量之间可以直接赋值,这会复制所有成员的值。
1 2 3 4 5 6 7 8 9 10
| struct Point p1 = {10, 20}; struct Point p2 = p1; printf("p2: (%d, %d)\n", p2.x, p2.y);
struct Point *pp1 = &p1; struct Point *pp2 = pp1; pp2->x = 100; printf("p1.x: %d\n", p1.x);
|
1.5.2 结构体比较
C 语言不支持直接比较结构体变量,需要逐个成员进行比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| struct Point p1 = {10, 20}; struct Point p2 = {10, 20};
if (p1.x == p2.x && p1.y == p2.y) { printf("p1 和 p2 相等\n"); } else { printf("p1 和 p2 不相等\n"); }
int compare_points(struct Point p1, struct Point p2) { if (p1.x != p2.x) { return p1.x - p2.x; } return p1.y - p2.y; }
if (compare_points(p1, p2) == 0) { printf("p1 和 p2 相等\n"); }
|
1.6 结构体数组
结构体数组是存储结构体变量的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| struct Student students[5];
struct Student students[] = { {"Alice", 18, 95.5}, {"Bob", 19, 88.5}, {"Charlie", 18, 92.0} };
for (int i = 0; i < 3; i++) { printf("姓名:%s, 年龄:%d, 分数:%.2f\n", students[i].name, students[i].age, students[i].score); }
struct Student *ptr = students; for (int i = 0; i < 3; i++) { printf("姓名:%s, 年龄:%d, 分数:%.2f\n", ptr[i].name, ptr[i].age, ptr[i].score); }
|
1.7 结构体作为函数参数
1.7.1 值传递
结构体作为函数参数时,默认是值传递,会复制整个结构体。
1 2 3 4 5 6 7 8 9 10 11 12
| void print_point(struct Point p) { printf("(%d, %d)\n", p.x, p.y); }
int main(void) { struct Point p = {10, 20}; print_point(p); return 0; }
|
1.7.2 地址传递
为了避免复制大型结构体,可以传递结构体的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void move_point(struct Point *pp, int dx, int dy) { pp->x += dx; pp->y += dy; }
int main(void) { struct Point p = {10, 20}; move_point(&p, 5, 5); printf("移动后:(%d, %d)\n", p.x, p.y); return 0; }
|
1.7.3 const 修饰符
使用 const 修饰符可以防止函数修改结构体的内容。
1 2 3 4 5 6 7
| void print_student(const struct Student *s) { printf("姓名:%s, 年龄:%d, 分数:%.2f\n", s->name, s->age, s->score); }
|
1.8 结构体作为函数返回值
1.8.1 返回结构体
函数可以返回结构体类型的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct Point create_point(int x, int y) { struct Point p; p.x = x; p.y = y; return p; }
int main(void) { struct Point p1 = create_point(10, 20); printf("p1: (%d, %d)\n", p1.x, p1.y); return 0; }
|
1.8.2 返回结构体指针
函数也可以返回结构体指针,但需要注意内存管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
struct Point *create_point_ptr1(int x, int y) { static struct Point p; p.x = x; p.y = y; return &p; }
struct Point *create_point_ptr2(int x, int y) { struct Point *p = (struct Point *)malloc(sizeof(struct Point)); if (p != NULL) { p->x = x; p->y = y; } return p; }
int main(void) { struct Point *p1 = create_point_ptr1(30, 40); printf("p1: (%d, %d)\n", p1->x, p1->y); struct Point *p2 = create_point_ptr2(50, 60); if (p2 != NULL) { printf("p2: (%d, %d)\n", p2->x, p2->y); free(p2); } return 0; }
|
1.9 结构体的高级特性
1.9.1 柔性数组成员
C99 引入了柔性数组成员,允许结构体的最后一个成员是一个未指定大小的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| struct Buffer { int size; char data[]; };
struct Buffer *create_buffer(int size) { struct Buffer *buf = (struct Buffer *)malloc(sizeof(struct Buffer) + size * sizeof(char)); if (buf != NULL) { buf->size = size; } return buf; }
void free_buffer(struct Buffer *buf) { free(buf); }
int main(void) { struct Buffer *buf = create_buffer(100); if (buf != NULL) { strcpy(buf->data, "Hello, World!"); printf("数据:%s\n", buf->data); free_buffer(buf); } return 0; }
|
1.9.2 结构体的位字段
结构体可以包含位字段,用于指定成员占用的位数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct Flags { unsigned int flag1 : 1; unsigned int flag2 : 2; unsigned int flag3 : 3; unsigned int : 2; unsigned int flag4 : 8; };
struct Flags f; f.flag1 = 1; f.flag2 = 3; f.flag3 = 5; f.flag4 = 255;
printf("flag1: %d\n", f.flag1); printf("flag2: %d\n", f.flag2); printf("flag3: %d\n", f.flag3); printf("flag4: %d\n", f.flag4); printf("结构体大小:%zu 字节\n", sizeof(struct Flags));
|
2. 共用体的深入理解
2.1 共用体的基本概念
共用体(Union)是一种特殊的数据类型,它允许在同一块内存空间中存储不同类型的数据。共用体的所有成员共享同一块内存,修改一个成员会影响其他成员。
2.1.1 共用体的内存布局
共用体的大小等于其最大成员的大小,所有成员都从同一个内存地址开始存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| union Data { int i; float f; char c; };
printf("共用体大小:%zu 字节\n", sizeof(union Data));
|
2.2 共用体的声明和使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| union union_name { member_type1 member_name1; member_type2 member_name2; };
union Data { int i; float f; char c; };
union Data data;
data.i = 100; printf("data.i = %d\n", data.i);
data.f = 3.14; printf("data.f = %.2f\n", data.f); printf("data.i = %d\n", data.i);
|
2.3 共用体的应用
2.3.1 节省内存
当不同类型的数据不需要同时使用时,使用共用体可以节省内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct Value { enum { INT, FLOAT, STRING } type; union { int i; float f; char *s; } data; };
struct Value v; v.type = INT; v.data.i = 100; printf("值:%d\n", v.data.i);
v.type = FLOAT; v.data.f = 3.14; printf("值:%.2f\n", v.data.f);
v.type = STRING; v.data.s = "Hello"; printf("值:%s\n", v.data.s);
|
2.3.2 类型转换
共用体可以用于在不同类型之间转换数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| union FloatInt { float f; int i; };
void print_float_bits(float f) { union FloatInt fi; fi.f = f; printf("浮点数 %.2f 的二进制表示:\n", f); for (int j = 31; j >= 0; j--) { printf("%d", (fi.i >> j) & 1); if (j == 31 || j == 23) printf(" "); } printf("\n"); }
int main(void) { print_float_bits(3.14); return 0; }
|
2.3.3 底层编程
共用体常用于底层编程,如访问硬件寄存器或处理二进制数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| union Register { unsigned int value; struct { unsigned int bit0 : 1; unsigned int bit1 : 1; unsigned int bit2 : 1; unsigned int bit3 : 1; unsigned int bit4_7 : 4; unsigned int bit8_15 : 8; unsigned int bit16_31 : 16; } bits; };
union Register reg; reg.value = 0x12345678; printf("寄存器值:0x%08X\n", reg.value); printf("bit0: %d\n", reg.bits.bit0); printf("bit1: %d\n", reg.bits.bit1); printf("bit2: %d\n", reg.bits.bit2); printf("bit3: %d\n", reg.bits.bit3); printf("bit4_7: %d\n", reg.bits.bit4_7); printf("bit8_15: %d\n", reg.bits.bit8_15); printf("bit16_31: %d\n", reg.bits.bit16_31);
|
3. 枚举的深入理解
3.1 枚举的基本概念
枚举(Enumeration)是一种用户定义的整数类型,它由一组命名的常量组成。枚举常量默认从 0 开始,依次递增。
3.2 枚举的声明和使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| enum enum_name { constant1, constant2, };
enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
enum Weekday today = WEDNESDAY;
printf("今天是星期 %d\n", today);
enum Color { RED = 1, GREEN = 2, BLUE = 4 };
enum Color c = GREEN; printf("颜色值:%d\n", c);
|
3.3 枚举的高级应用
3.3.1 枚举的位掩码
枚举可以用于定义位掩码,方便进行位运算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| enum FilePermissions { READ = 0x01, WRITE = 0x02, EXECUTE = 0x04, ALL = READ | WRITE | EXECUTE };
void check_permissions(int permissions) { if (permissions & READ) { printf("有读权限\n"); } if (permissions & WRITE) { printf("有写权限\n"); } if (permissions & EXECUTE) { printf("有执行权限\n"); } }
int main(void) { int permissions = READ | WRITE; check_permissions(permissions); return 0; }
|
3.3.2 枚举与字符串的转换
可以编写函数在枚举值和字符串之间进行转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| enum Color { RED, GREEN, BLUE, YELLOW, PURPLE, COLOR_COUNT };
const char *color_names[] = { "红色", "绿色", "蓝色", "黄色", "紫色" };
const char *color_to_string(enum Color color) { if (color >= 0 && color < COLOR_COUNT) { return color_names[color]; } return "未知颜色"; }
enum Color string_to_color(const char *str) { for (int i = 0; i < COLOR_COUNT; i++) { if (strcmp(str, color_names[i]) == 0) { return (enum Color)i; } } return COLOR_COUNT; }
int main(void) { enum Color c = RED; printf("枚举值:%d, 字符串:%s\n", c, color_to_string(c)); const char *str = "绿色"; enum Color c2 = string_to_color(str); printf("字符串:%s, 枚举值:%d\n", str, c2); return 0; }
|
3.4 枚举的优点
- 代码可读性 - 使用有意义的名称代替数字
- 类型安全 - 编译器会检查枚举类型的使用
- 维护性 - 便于修改和扩展
- 代码提示 - 编辑器可以提供枚举常量的代码提示
4. 类型定义
使用 typedef 关键字为现有类型创建别名,提高代码的可读性和可维护性。
4.1 基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| typedef int Integer; typedef float Real;
typedef struct { int x; int y; } Point;
typedef int *IntPtr; typedef char *String;
typedef int IntArray[10]; typedef char StringArray[5][50];
Integer age = 20; Real pi = 3.14; Point p = {10, 20}; IntPtr ptr = &age; String name = "Alice";
IntArray numbers; for (int i = 0; i < 10; i++) { numbers[i] = i; }
StringArray names = { "Alice", "Bob", "Charlie", "David", "Eve" };
|
4.2 为复杂类型创建别名
4.2.1 为函数指针创建别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| typedef int (*Operation)(int, int); typedef void (*Callback)(void *data);
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
void print_callback(void *data) { printf("回调函数被调用,数据:%s\n", (char *)data); }
int main(void) { Operation op1 = add; Operation op2 = subtract; printf("5 + 3 = %d\n", op1(5, 3)); printf("5 - 3 = %d\n", op2(5, 3)); Callback cb = print_callback; cb("Hello"); return 0; }
|
4.2.2 为结构体指针创建别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| typedef struct Node { int data; struct Node *next; } Node, *NodePtr;
NodePtr create_node(int data) { NodePtr node = (NodePtr)malloc(sizeof(Node)); if (node != NULL) { node->data = data; node->next = NULL; } return node; }
void free_list(NodePtr head) { while (head != NULL) { NodePtr temp = head; head = head->next; free(temp); } }
int main(void) { NodePtr head = create_node(10); head->next = create_node(20); head->next->next = create_node(30); NodePtr current = head; while (current != NULL) { printf("%d ", current->data); current = current->next; } printf("\n"); free_list(head); return 0; }
|
4.3 类型定义的最佳实践
- 使用有意义的名称 - 别名应该能够反映类型的用途
- 保持一致性 - 在整个项目中使用一致的命名约定
- 避免过度使用 - 不要为所有类型都创建别名,只在必要时使用
- 注意作用域 - 类型定义的作用域与变量相同,通常在头文件中声明
5. 位域的深入应用
5.1 位域的基本概念
位域是一种特殊的结构体成员,它允许指定成员占用的位数,从而节省内存空间。
5.2 位域的声明和使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct Flags { unsigned int flag1 : 1; unsigned int flag2 : 2; unsigned int flag3 : 3; unsigned int : 2; unsigned int flag4 : 8; };
struct Flags f; f.flag1 = 1; f.flag2 = 3; f.flag3 = 5; f.flag4 = 255;
printf("flag1: %d\n", f.flag1); printf("flag2: %d\n", f.flag2); printf("flag3: %d\n", f.flag3); printf("flag4: %d\n", f.flag4); printf("结构体大小:%zu 字节\n", sizeof(struct Flags));
|
5.3 位域的应用
5.3.1 节省内存
当需要存储多个布尔值或小整数时,使用位域可以显著节省内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct PersonInfo { unsigned int is_student : 1; unsigned int is_employee : 1; unsigned int is_married : 1; unsigned int gender : 1; unsigned int age : 7; };
printf("结构体大小:%zu 字节\n", sizeof(struct PersonInfo));
struct PersonInfo info; info.is_student = 1; info.is_employee = 0; info.is_married = 0; info.gender = 1; info.age = 25;
printf("是否学生:%d\n", info.is_student); printf("是否员工:%d\n", info.is_employee); printf("是否已婚:%d\n", info.is_married); printf("性别:%s\n", info.gender ? "女" : "男"); printf("年龄:%d\n", info.age);
|
5.3.2 硬件编程
位域常用于硬件编程,访问硬件寄存器的特定位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| struct UART_Registers { unsigned int data : 8; unsigned int parity : 2; unsigned int stop_bits : 2; unsigned int baud_rate : 4; };
#define UART_BASE 0x1000 #define UART_REG ((struct UART_Registers *)UART_BASE)
void init_uart(void) { UART_REG->data = 0x41; UART_REG->parity = 0; UART_REG->stop_bits = 1; UART_REG->baud_rate = 3; }
|
5.3.3 协议实现
位域常用于实现网络协议或文件格式中的位级字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| struct IP_Header { unsigned int version : 4; unsigned int header_length : 4; unsigned int tos : 8; unsigned int total_length : 16; unsigned int identification : 16; unsigned int flags : 3; unsigned int fragment_offset : 13; unsigned int ttl : 8; unsigned int protocol : 8; unsigned int checksum : 16; unsigned int source_ip : 32; unsigned int destination_ip : 32; };
void parse_ip_header(const unsigned char *data) { struct IP_Header *header = (struct IP_Header *)data; printf("IP 版本:%d\n", header->version); printf("头部长度:%d\n", header->header_length); printf("总长度:%d\n", header->total_length); printf("TTL:%d\n", header->ttl); printf("协议:%d\n", header->protocol); }
|
6. 复合数据类型的性能考虑
6.1 结构体的性能
- 内存访问 - 结构体成员的访问速度与普通变量相同
- 函数参数 - 传递大型结构体时,使用指针可以避免复制开销
- 内存对齐 - 合理安排成员顺序可以减少内存浪费
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
struct BadStruct { char c; int i; char d; };
struct GoodStruct { int i; char c; char d; };
printf("BadStruct 大小:%zu 字节\n", sizeof(struct BadStruct)); printf("GoodStruct 大小:%zu 字节\n", sizeof(struct GoodStruct));
|
6.2 共用体的性能
- 内存访问 - 共用体成员的访问速度与普通变量相同
- 内存使用 - 共用体可以节省内存,特别是当不同类型的数据不需要同时使用时
6.3 位域的性能
- 内存使用 - 位域可以显著节省内存
- 访问速度 - 位域的访问速度可能比普通成员稍慢,因为需要进行位运算
7. 复合数据类型的高级应用示例
7.1 示例 1:学生管理系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include <stdio.h> #include <string.h> #include <stdlib.h>
typedef struct { char name[50]; int age; float scores[3]; float average; } Student;
void calculate_average(Student *s) { float sum = 0; for (int i = 0; i < 3; i++) { sum += s->scores[i]; } s->average = sum / 3; }
void print_student(const Student *s) { printf("姓名:%s\n", s->name); printf("年龄:%d\n", s->age); printf("分数:%.2f %.2f %.2f\n", s->scores[0], s->scores[1], s->scores[2]); printf("平均分:%.2f\n\n", s->average); }
void sort_students(Student *students, int count) { for (int i = 0; i < count - 1; i++) { for (int j = 0; j < count - i - 1; j++) { if (students[j].average < students[j + 1].average) { Student temp = students[j]; students[j] = students[j + 1]; students[j + 1] = temp; } } } }
int main(void) { Student students[] = { {"Alice", 18, {95.5, 88.0, 92.5}, 0}, {"Bob", 19, {82.0, 76.5, 88.5}, 0}, {"Charlie", 18, {90.0, 94.5, 89.0}, 0}, {"David", 19, {78.5, 82.0, 85.5}, 0}, {"Eve", 18, {92.0, 88.5, 90.5}, 0} }; int num_students = sizeof(students) / sizeof(students[0]); for (int i = 0; i < num_students; i++) { calculate_average(&students[i]); } sort_students(students, num_students); printf("按平均分排序后的学生信息:\n\n"); for (int i = 0; i < num_students; i++) { printf("排名 %d:\n", i + 1); print_student(&students[i]); } return 0; }
|
7.2 示例 2:使用枚举和结构体实现状态机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| #include <stdio.h>
enum State { STATE_IDLE, STATE_READING, STATE_PROCESSING, STATE_WRITING, STATE_DONE };
const char *state_names[] = { "空闲", "读取", "处理", "写入", "完成" };
typedef struct { enum State current_state; int data; int result; } StateMachine;
void init_state_machine(StateMachine *sm) { sm->current_state = STATE_IDLE; sm->data = 0; sm->result = 0; }
void process_state_machine(StateMachine *sm) { switch (sm->current_state) { case STATE_IDLE: printf("状态:%s\n", state_names[sm->current_state]); sm->current_state = STATE_READING; break; case STATE_READING: printf("状态:%s\n", state_names[sm->current_state]); sm->data = 42; sm->current_state = STATE_PROCESSING; break; case STATE_PROCESSING: printf("状态:%s\n", state_names[sm->current_state]); sm->result = sm->data * 2; sm->current_state = STATE_WRITING; break; case STATE_WRITING: printf("状态:%s\n", state_names[sm->current_state]); printf("处理结果:%d\n", sm->result); sm->current_state = STATE_DONE; break; case STATE_DONE: printf("状态:%s\n", state_names[sm->current_state]); break; default: printf("未知状态\n"); sm->current_state = STATE_IDLE; break; } }
int main(void) { StateMachine sm; init_state_machine(&sm); while (sm.current_state != STATE_DONE) { process_state_machine(&sm); } return 0; }
|
7.3 示例 3:使用共用体和结构体实现变体类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| #include <stdio.h> #include <string.h> #include <stdlib.h>
enum ValueType { TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_BOOL };
typedef struct { enum ValueType type; union { int i; float f; char *s; int b; } data; } Variant;
Variant create_int_variant(int value) { Variant v; v.type = TYPE_INT; v.data.i = value; return v; }
Variant create_float_variant(float value) { Variant v; v.type = TYPE_FLOAT; v.data.f = value; return v; }
Variant create_string_variant(const char *value) { Variant v; v.type = TYPE_STRING; v.data.s = strdup(value); return v; }
Variant create_bool_variant(int value) { Variant v; v.type = TYPE_BOOL; v.data.b = value; return v; }
void destroy_variant(Variant *v) { if (v->type == TYPE_STRING) { free(v->data.s); } }
void print_variant(const Variant *v) { switch (v->type) { case TYPE_INT: printf("整型:%d\n", v->data.i); break; case TYPE_FLOAT: printf("浮点型:%.2f\n", v->data.f); break; case TYPE_STRING: printf("字符串:%s\n", v->data.s); break; case TYPE_BOOL: printf("布尔型:%s\n", v->data.b ? "真" : "假"); break; default: printf("未知类型\n"); break; } }
int main(void) { Variant v1 = create_int_variant(42); Variant v2 = create_float_variant(3.14); Variant v3 = create_string_variant("Hello, World!"); Variant v4 = create_bool_variant(1); print_variant(&v1); print_variant(&v2); print_variant(&v3); print_variant(&v4); destroy_variant(&v3); return 0; }
|
8. 小结
本章深入介绍了 C 语言中的复合数据类型,包括结构体、共用体、枚举、类型定义和位域。这些复合数据类型允许我们创建更复杂的数据结构,以适应各种编程需求。
8.1 关键知识点
- 结构体:一种复合数据类型,可以包含不同类型的成员变量,成员在内存中连续存储
- 共用体:一种特殊的数据类型,所有成员共享同一块内存,大小等于最大成员的大小
- 枚举:一种用户定义的整数类型,由一组命名的常量组成
- 类型定义:使用
typedef 关键字为现有类型创建别名,提高代码的可读性和可维护性 - 位域:一种特殊的结构体成员,允许指定成员占用的位数,从而节省内存空间
8.2 学习建议
- 多写代码:通过实际编程练习掌握复合数据类型的使用
- 理解内存布局:理解结构体、共用体和位域的内存布局,有助于编写更高效的代码
- 注意内存管理:使用动态内存分配时,要注意及时释放内存,避免内存泄漏
- 遵循最佳实践:合理使用复合数据类型,提高代码的可读性和可维护性
- 学习设计模式:学习如何使用复合数据类型实现常见的设计模式,如状态模式、策略模式等
复合数据类型是 C 语言编程的重要组成部分,掌握好这些数据类型对于编写高质量的 C 程序至关重要。通过本章的学习,希望读者能够深入理解复合数据类型的概念,掌握它们的使用方法,以及编写更加高效、安全的 C 程序。
在后续章节中,我们将学习内存管理、文件输入/输出等高级主题,这些内容将进一步扩展你的 C 语言编程能力。