第9章 结构体与联合体

1. 结构体的深入理解

1.1 结构体的基本概念

结构体是一种复合数据类型,它可以包含不同类型的成员变量,这些成员变量被组织在一起,形成一个有意义的数据单元。

1.1.1 结构体的内存布局

结构体的成员在内存中是连续存储的,每个成员的内存地址都按照其声明的顺序依次排列。编译器会根据成员的类型和对齐要求来分配内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Point
{
int x; // 4 字节
int y; // 4 字节
};

// 内存布局:
// +--------+--------+
// | x | y |
// +--------+--------+
// 0 4 8

printf("结构体大小:%zu 字节\n", sizeof(struct Point)); // 输出 8

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); // 输出 100

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
// 使用 const 修饰符
void print_student(const struct Student *s)
{
printf("姓名:%s, 年龄:%d, 分数:%.2f\n",
s->name, s->age, s->score);
// s->age = 20; // 错误:不能修改 const 结构体
}

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
// 结构体指针作为函数返回值(注意内存管理)

// 方法 1:使用静态变量
struct Point *create_point_ptr1(int x, int y)
{
static struct Point p; // 使用静态变量
p.x = x;
p.y = y;
return &p;
}

// 方法 2:使用动态内存分配
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)
{
// 使用方法 1
struct Point *p1 = create_point_ptr1(30, 40);
printf("p1: (%d, %d)\n", p1->x, p1->y);

// 使用方法 2
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; // 1 位
unsigned int flag2 : 2; // 2 位
unsigned int flag3 : 3; // 3 位
unsigned int : 2; // 2 位填充
unsigned int flag4 : 8; // 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; // 4 字节
float f; // 4 字节
char c; // 1 字节
};

// 内存布局:
// +--------+
// | i/f/c |
// +--------+
// 0 4

printf("共用体大小:%zu 字节\n", sizeof(union Data)); // 输出 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
// 声明共用体类型
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); // 输出 2

// 自定义枚举值
enum Color
{
RED = 1,
GREEN = 2,
BLUE = 4
};

// 使用枚举
enum Color c = GREEN;
printf("颜色值:%d\n", c); // 输出 2

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, // 1 << 0
WRITE = 0x02, // 1 << 1
EXECUTE = 0x04, // 1 << 2
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; // 1 位
unsigned int flag2 : 2; // 2 位
unsigned int flag3 : 3; // 3 位
unsigned int : 2; // 2 位填充
unsigned int flag4 : 8; // 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; // 0: 男, 1: 女
unsigned int age : 7; // 0-127
};

printf("结构体大小:%zu 字节\n", sizeof(struct PersonInfo)); // 输出 2

// 使用位域
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;// 波特率选择
};

// 假设 UART 寄存器地址为 0x1000
#define UART_BASE 0x1000
#define UART_REG ((struct UART_Registers *)UART_BASE)

// 使用位域访问寄存器
void init_uart(void)
{
UART_REG->data = 0x41; // 'A'
UART_REG->parity = 0; // 无校验
UART_REG->stop_bits = 1; // 1 个停止位
UART_REG->baud_rate = 3; // 9600 bps
}

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; // IP 版本
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; // 源 IP 地址
unsigned int destination_ip : 32; // 目的 IP 地址
};

// 使用位域解析 IP 头部
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; // 1 字节
int i; // 4 字节
char d; // 1 字节
};

// 合理的成员顺序
struct GoodStruct
{
int i; // 4 字节
char c; // 1 字节
char d; // 1 字节
};

printf("BadStruct 大小:%zu 字节\n", sizeof(struct BadStruct)); // 可能是 12
printf("GoodStruct 大小:%zu 字节\n", sizeof(struct GoodStruct)); // 可能是 8

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 语言编程能力。