C语言结构体和联合体

概述

本文档详细介绍C语言中结构体和联合体的使用方法,包括结构体的定义、初始化、结构体数组、结构体指针、嵌套结构体、位域结构体、联合体的定义和使用,以及枚举与结构体的结合使用等内容。结构体和联合体是C语言中重要的数据结构,它们允许我们将不同类型的数据组合在一起,提高代码的组织性和可读性。

结构体的基本概念

结构体的定义

结构体是一种用户自定义的数据类型,它可以包含不同类型的成员变量。结构体的定义语法如下:

1
2
3
4
5
struct 结构体名 {
类型 成员名1;
类型 成员名2;
// 更多成员...
};

结构体变量的声明

结构体变量的声明语法如下:

1
struct 结构体名 变量名;

示例代码

1
2
3
4
5
6
7
8
9
10
// 学生结构体定义
struct Student {
char name[50]; // 姓名
int age; // 年龄
int student_id; // 学号
float score; // 成绩
};

// 声明结构体变量
struct Student student1;

结构体的定义与初始化

结构体成员的赋值

可以使用点运算符(.)访问和修改结构体成员:

1
2
3
4
5
// 结构体成员赋值
strcpy(student1.name, "张三");
student1.age = 20;
student1.student_id = 1001;
student1.score = 95.5;

结构体变量的初始化

结构体变量可以在声明时进行初始化,有以下几种方式:

  1. 按顺序初始化:按照结构体成员的声明顺序进行初始化
  2. 指定初始化器:使用成员名进行初始化(C99及以上)

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 按顺序初始化结构体
struct Student student2 = {
"李四",
21,
1002,
88.0
};

// 使用指定初始化器(C99及以上)
struct Student student3 = {
.name = "王五",
.student_id = 1003,
.score = 92.5,
.age = 22
};

补充示例:结构体的嵌套初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 日期结构体
struct Date {
int year;
int month;
int day;
};

// 员工结构体(嵌套结构体)
struct Employee {
char name[50];
int employee_id;
float salary;
struct Date hire_date; // 嵌套日期结构体
};

// 嵌套结构体初始化
struct Employee emp = {
"王五",
2001,
5000.0,
{2020, 3, 15} // 嵌套结构体初始化
};

结构体数组

结构体数组的定义

结构体数组是元素为结构体的数组,定义语法如下:

1
struct 结构体名 数组名[数组大小];

结构体数组的初始化

结构体数组可以在声明时进行初始化:

1
2
3
4
5
6
// 结构体数组初始化
struct Student students[] = {
{"赵六", 20, 1004, 85.5},
{"孙七", 21, 1005, 90.0},
{"周八", 22, 1006, 78.5}
};

访问结构体数组元素

可以使用下标访问结构体数组的元素,然后使用点运算符访问成员:

1
2
3
4
5
6
7
8
9
10
// 访问结构体数组元素
int num_students = sizeof(students) / sizeof(students[0]);

for (int i = 0; i < num_students; i++) {
printf("学生 %d:\n", i + 1);
printf("姓名:%s\n", students[i].name);
printf("年龄:%d\n", students[i].age);
printf("学号:%d\n", students[i].student_id);
printf("成绩:%.1f\n", students[i].score);
}

补充示例:结构体数组的排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 结构体数组排序(按成绩降序)
void sort_students_by_score(struct Student arr[], int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j].score < arr[j + 1].score) {
// 交换元素
struct Student temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

// 使用排序函数
sort_students_by_score(students, num_students);

// 打印排序后的结果
printf("按成绩排序后的学生信息:\n");
for (int i = 0; i < num_students; i++) {
printf("第%d名:%s,成绩:%.1f\n", i + 1, students[i].name, students[i].score);
}

结构体指针

结构体指针的定义

结构体指针是指向结构体的指针,定义语法如下:

1
struct 结构体名 *指针变量名;

结构体指针的初始化与使用

结构体指针可以指向结构体变量,使用&运算符获取结构体变量的地址:

1
2
3
4
5
6
// 结构体指针声明和初始化
struct Student *student_ptr = &student1;

// 通过指针访问结构体成员(两种方式)
printf("姓名:%s\n", student_ptr->name); // 箭头运算符
printf("年龄:%d\n", (*student_ptr).age); // 解引用后使用点运算符

修改结构体指针指向的成员

可以通过结构体指针修改结构体成员的值:

1
2
3
// 使用结构体指针更新成员
student_ptr->score = 98.0;
printf("更新后学生1成绩:%.1f\n", student1.score);

补充示例:动态分配结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 动态分配结构体
struct Student *dynamic_student = (struct Student *)malloc(sizeof(struct Student));
if (dynamic_student == NULL) {
printf("内存分配失败\n");
return 1;
}

// 初始化动态分配的结构体
strcpy(dynamic_student->name, "动态学生");
dynamic_student->age = 20;
dynamic_student->student_id = 2000;
dynamic_student->score = 90.0;

// 打印动态分配的结构体
printf("动态分配的学生信息:\n");
printf("姓名:%s\n", dynamic_student->name);
printf("年龄:%d\n", dynamic_student->age);
printf("学号:%d\n", dynamic_student->student_id);
printf("成绩:%.1f\n", dynamic_student->score);

// 释放内存
free(dynamic_student);
dynamic_student = NULL;

嵌套结构体

嵌套结构体的定义

嵌套结构体是指在一个结构体中包含另一个结构体作为成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 日期结构体
struct Date {
int year;
int month;
int day;
};

// 员工结构体(嵌套结构体示例)
struct Employee {
char name[50];
int employee_id;
float salary;
struct Date hire_date; // 嵌套日期结构体
};

嵌套结构体的初始化与访问

嵌套结构体的初始化和访问需要使用点运算符链式访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 初始化嵌套结构体
struct Employee emp = {
"王五",
2001,
5000.0,
{2020, 3, 15} // 嵌套结构体初始化
};

// 访问嵌套结构体成员
printf("入职日期:%d-%d-%d\n", emp.hire_date.year, emp.hire_date.month, emp.hire_date.day);

// 修改嵌套结构体成员
emp.hire_date.year = 2021;
printf("修改后入职日期:%d-%d-%d\n", emp.hire_date.year, emp.hire_date.month, emp.hire_date.day);

补充示例:多层嵌套结构体

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 Address {
char street[100];
char city[50];
char zip_code[10];
};

struct Person {
char name[50];
int age;
struct Address address; // 嵌套地址结构体
};

struct Company {
char name[100];
struct Person CEO; // 嵌套人员结构体
};

// 初始化多层嵌套结构体
struct Company company = {
"科技公司",
{
"张三",
45,
{
"科技路1号",
"北京",
"100000"
}
}
};

// 访问多层嵌套结构体成员
printf("公司名称:%s\n", company.name);
printf("CEO姓名:%s\n", company.CEO.name);
printf("CEO地址:%s, %s, %s\n", company.CEO.address.street, company.CEO.address.city, company.CEO.address.zip_code);

位域结构体

位域的基本概念

位域是指在结构体中使用冒号(:)指定成员所占的位数,用于节省内存空间:

1
2
3
4
5
6
7
// 位域结构体示例
struct Flags {
unsigned int is_active : 1; // 1位:是否激活
unsigned int is_admin : 1; // 1位:是否管理员
unsigned int user_type : 2; // 2位:用户类型(0-3)
unsigned int permissions : 4; // 4位:权限标志
};

位域的使用

位域的使用方式与普通结构体成员相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 初始化位域结构体
struct Flags flags = {
1, // is_active = true
0, // is_admin = false
2, // user_type = 2
10 // permissions = 10 (二进制:1010)
};

// 访问位域成员
printf("is_active:%d\n", flags.is_active);
printf("is_admin:%d\n", flags.is_admin);
printf("user_type:%d\n", flags.user_type);
printf("permissions:%d\n", flags.permissions);

// 修改位域成员
flags.is_admin = 1;
printf("修改后is_admin:%d\n", flags.is_admin);

// 显示位域结构体的大小
printf("位域结构体大小:%zu 字节\n", sizeof(struct Flags));

补充示例:位域的应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 位域的应用场景:网络协议头
struct EthernetHeader {
unsigned int destination : 48; // 目的MAC地址(6字节)
unsigned int source : 48; // 源MAC地址(6字节)
unsigned int type : 16; // 类型字段(2字节)
};

// 位域的应用场景:硬件寄存器
struct StatusRegister {
unsigned int ready : 1; // 就绪标志
unsigned int error : 1; // 错误标志
unsigned int mode : 2; // 模式选择
unsigned int reserved : 4; // 保留位
};

// 使用状态寄存器位域
struct StatusRegister status = {1, 0, 2, 0};
printf("状态寄存器:\n");
printf("就绪:%d\n", status.ready);
printf("错误:%d\n", status.error);
printf("模式:%d\n", status.mode);

联合体

联合体的基本概念

联合体是一种特殊的数据类型,它的所有成员共享同一块内存空间,每次只能使用其中一个成员:

1
2
3
4
5
6
// 联合体示例
union Data {
int i; // 整型
float f; // 浮点型
char str[20]; // 字符数组
};

联合体的使用

联合体的使用方式与结构体类似,但需要注意成员共享内存的特性:

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
// 联合体变量声明和初始化
union Data data;

// 赋值为整型
data.i = 123;
printf("联合体存储整型:\n");
printf("整型值:%d\n", data.i);
// printf("浮点值:%.2f\n", data.f); // 注意:这里的值是不确定的
// printf("字符串:%s\n", data.str); // 注意:这里的值是不确定的

// 赋值为浮点型(会覆盖之前的整型值)
data.f = 3.14159;
printf("\n联合体存储浮点型:\n");
// printf("整型值:%d\n", data.i); // 注意:这里的值是不确定的
printf("浮点值:%.2f\n", data.f);
// printf("字符串:%s\n", data.str); // 注意:这里的值是不确定的

// 赋值为字符串(会覆盖之前的浮点型值)
strcpy(data.str, "Hello, Union!");
printf("\n联合体存储字符串:\n");
// printf("整型值:%d\n", data.i); // 注意:这里的值是不确定的
// printf("浮点值:%.2f\n", data.f); // 注意:这里的值是不确定的
printf("字符串:%s\n", data.str);

// 显示联合体的大小
printf("\n联合体大小:%zu 字节\n", sizeof(union Data));

补充示例:联合体的应用场景

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
// 联合体的应用场景:类型转换
union TypeConverter {
int i;
float f;
unsigned char bytes[4];
};

// 使用联合体进行类型转换
union TypeConverter converter;
converter.f = 3.14159;

printf("浮点数:%.2f\n", converter.f);
printf("对应的整型:%d\n", converter.i);
printf("对应的字节:");
for (int j = 0; j < 4; j++) {
printf("%02X ", converter.bytes[j]);
}
printf("\n");

// 联合体的应用场景:节省内存
struct Node {
int type; // 节点类型
union {
int i_value; // 当type为0时使用
float f_value; // 当type为1时使用
char *s_value; // 当type为2时使用
} value;
};

// 使用节点结构体
struct Node node1 = {0, {.i_value = 100}};
struct Node node2 = {1, {.f_value = 3.14}};
struct Node node3 = {2, {.s_value = "Hello"}};

printf("节点1:类型=%d, 值=%d\n", node1.type, node1.value.i_value);
printf("节点2:类型=%d, 值=%.2f\n", node2.type, node2.value.f_value);
printf("节点3:类型=%d, 值=%s\n", node3.type, node3.value.s_value);

枚举与结构体

枚举类型的定义

枚举是一种用户定义的整数类型,用于表示一组命名的常量:

1
2
3
4
5
6
7
8
// 枚举类型示例
enum Color {
RED, // 0
GREEN, // 1
BLUE, // 2
YELLOW, // 3
BLACK // 4
};

枚举与结构体结合使用

枚举可以作为结构体的成员类型:

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
// 带有枚举成员的结构体
struct Shape {
enum Color color;
float area;
char name[20];
};

// 初始化带有枚举成员的结构体
struct Shape circle = {
RED,
78.5,
"Circle"
};

struct Shape rectangle = {
BLUE,
120.0,
"Rectangle"
};

// 访问结构体中的枚举成员
printf("图形1:\n");
printf("名称:%s\n", circle.name);
printf("颜色:%d\n", circle.color); // 枚举值为整数
printf("面积:%.1f\n", circle.area);

printf("\n图形2:\n");
printf("名称:%s\n", rectangle.name);
printf("颜色:%d\n", rectangle.color);
printf("面积:%.1f\n", rectangle.area);

补充示例:枚举的高级使用

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
// 枚举的高级使用:指定值
enum Weekday {
MONDAY = 1,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};

// 带有枚举的结构体
struct Appointment {
char description[100];
enum Weekday day;
int hour;
int minute;
};

// 初始化约会结构体
struct Appointment meeting = {
"团队会议",
MONDAY,
10,
30
};

// 打印约会信息
printf("约会:%s\n", meeting.description);
printf("时间:周%d %d:%02d\n", meeting.day, meeting.hour, meeting.minute);

结构体作为函数参数

值传递与指针传递

结构体可以作为函数参数传递,有两种传递方式:

  1. 值传递:将结构体的副本传递给函数,函数内部对结构体的修改不会影响原始结构体
  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
// 打印学生信息(值传递)
void print_student(struct Student s) {
printf("姓名:%s\n", s.name);
printf("年龄:%d\n", s.age);
printf("学号:%d\n", s.student_id);
printf("成绩:%.1f\n", s.score);
}

// 更新学生成绩(指针传递)
void update_student_score(struct Student *s, float new_score) {
s->score = new_score;
}

// 使用函数
struct Student student = {"张三", 20, 1001, 95.5};

// 值传递
printf("通过值传递结构体:\n");
print_student(student);

// 指针传递
printf("\n通过指针传递结构体更新成绩:\n");
printf("更新前成绩:%.1f\n", student.score);
update_student_score(&student, 99.5);
printf("更新后成绩:%.1f\n", student.score);

补充示例:结构体作为函数返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 结构体作为函数返回值
struct Student create_student(char *name, int age, int student_id, float score) {
struct Student s;
strcpy(s.name, name);
s.age = age;
s.student_id = student_id;
s.score = score;
return s;
}

// 使用函数创建学生
struct Student new_student = create_student("新学生", 21, 1007, 89.5);
printf("创建的学生信息:\n");
print_student(new_student);

结构体的高级应用

结构体与链表

结构体可以用于实现链表等数据结构:

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
// 链表节点结构体
struct Node {
int data;
struct Node *next;
};

// 创建新节点
struct Node *create_node(int data) {
struct Node *new_node = (struct Node *)malloc(sizeof(struct Node));
if (new_node == NULL) {
return NULL;
}
new_node->data = data;
new_node->next = NULL;
return new_node;
}

// 添加节点到链表尾部
void append_node(struct Node **head, int data) {
struct Node *new_node = create_node(data);
if (new_node == NULL) {
return;
}

if (*head == NULL) {
*head = new_node;
return;
}

struct Node *current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = new_node;
}

// 打印链表
void print_list(struct Node *head) {
struct Node *current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}

// 释放链表
void free_list(struct Node *head) {
struct Node *temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}
}

// 使用链表
struct Node *head = NULL;
append_node(&head, 10);
append_node(&head, 20);
append_node(&head, 30);
append_node(&head, 40);

printf("链表:");
print_list(head);

// 释放链表
free_list(head);

结构体与文件I/O

结构体可以用于文件的读写操作:

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
// 结构体与文件I/O
void write_students_to_file(struct Student students[], int count, const char *filename) {
FILE *file = fopen(filename, "wb");
if (file == NULL) {
printf("无法打开文件\n");
return;
}

fwrite(students, sizeof(struct Student), count, file);
fclose(file);
}

void read_students_from_file(struct Student students[], int count, const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
printf("无法打开文件\n");
return;
}

fread(students, sizeof(struct Student), count, file);
fclose(file);
}

// 使用文件I/O
struct Student students_to_write[] = {
{"张三", 20, 1001, 95.5},
{"李四", 21, 1002, 88.0},
{"王五", 22, 1003, 92.5}
};

int count = sizeof(students_to_write) / sizeof(students_to_write[0]);

// 写入文件
write_students_to_file(students_to_write, count, "students.dat");
printf("学生信息已写入文件\n");

// 从文件读取
struct Student students_read[3];
read_students_from_file(students_read, count, "students.dat");

printf("从文件读取的学生信息:\n");
for (int i = 0; i < count; i++) {
printf("\n学生 %d:\n", i + 1);
print_student(students_read[i]);
}

最佳实践

  1. 使用typedef简化结构体声明:使用typedef为结构体创建别名,简化代码
  2. 合理设计结构体成员:将相关的数据成员组织在一起,提高代码的可读性
  3. 初始化结构体:总是初始化结构体,避免使用未初始化的成员
  4. 使用指针传递大型结构体:对于大型结构体,使用指针传递可以提高效率
  5. 注意结构体的内存对齐:了解结构体的内存对齐规则,合理安排成员顺序以节省内存
  6. 使用const修饰符:对于不需要修改的结构体参数,使用const修饰
  7. 避免结构体过大:如果结构体过大,考虑使用指针或分解为多个小结构体
  8. 使用位域节省内存:对于标志位等占用空间小的成员,使用位域
  9. 正确使用联合体:了解联合体的内存共享特性,避免错误使用
  10. 添加适当的注释:为结构体和联合体添加注释,说明其用途和成员含义

示例:使用typedef简化结构体声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用typedef简化结构体声明
typedef struct {
char name[50];
int age;
int id;
float salary;
} Employee; // Employee是结构体的别名

// 使用别名声明结构体变量
Employee emp1 = {"张三", 30, 1001, 5000.0};
Employee emp2 = {"李四", 25, 1002, 4500.0};

// 打印员工信息
printf("员工1:%s, %d岁, ID:%d, 工资:%.2f\n", emp1.name, emp1.age, emp1.id, emp1.salary);
printf("员工2:%s, %d岁, ID:%d, 工资:%.2f\n", emp2.name, emp2.age, emp2.id, emp2.salary);

综合示例

示例:学生管理系统

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 学生管理系统
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 学生结构体
typedef struct {
char name[50];
int id;
float score;
} Student;

// 函数原型
void add_student(Student **students, int *count);
void print_students(Student *students, int count);
void sort_students(Student *students, int count);
void save_students(Student *students, int count, const char *filename);
void load_students(Student **students, int *count, const char *filename);

int main() {
Student *students = NULL;
int count = 0;
int choice;

do {
printf("\n学生管理系统\n");
printf("1. 添加学生\n");
printf("2. 显示学生\n");
printf("3. 排序学生\n");
printf("4. 保存学生到文件\n");
printf("5. 从文件加载学生\n");
printf("0. 退出\n");
printf("请选择:");
scanf("%d", &choice);

switch (choice) {
case 1:
add_student(&students, &count);
break;
case 2:
print_students(students, count);
break;
case 3:
sort_students(students, count);
printf("学生已排序\n");
break;
case 4:
save_students(students, count, "students.dat");
break;
case 5:
load_students(&students, &count, "students.dat");
break;
case 0:
printf("退出系统\n");
break;
default:
printf("无效选择\n");
}
} while (choice != 0);

// 释放内存
free(students);

return 0;
}

// 添加学生
void add_student(Student **students, int *count) {
// 重新分配内存
*students = (Student *)realloc(*students, (*count + 1) * sizeof(Student));
if (*students == NULL) {
printf("内存分配失败\n");
return;
}

// 输入学生信息
printf("请输入学生姓名:");
scanf("%s", (*students)[*count].name);
printf("请输入学生ID:");
scanf("%d", &(*students)[*count].id);
printf("请输入学生成绩:");
scanf("%f", &(*students)[*count].score);

(*count)++;
printf("学生添加成功\n");
}

// 打印学生信息
void print_students(Student *students, int count) {
if (count == 0) {
printf("没有学生信息\n");
return;
}

printf("\n学生信息:\n");
for (int i = 0; i < count; i++) {
printf("姓名:%s, ID:%d, 成绩:%.1f\n", students[i].name, students[i].id, students[i].score);
}
}

// 排序学生(按成绩降序)
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].score < students[j + 1].score) {
Student temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
}

// 保存学生到文件
void save_students(Student *students, int count, const char *filename) {
FILE *file = fopen(filename, "wb");
if (file == NULL) {
printf("无法打开文件\n");
return;
}

fwrite(&count, sizeof(int), 1, file);
fwrite(students, sizeof(Student), count, file);
fclose(file);
printf("学生信息已保存到文件\n");
}

// 从文件加载学生
void load_students(Student **students, int *count, const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
printf("无法打开文件\n");
return;
}

// 读取学生数量
fread(count, sizeof(int), 1, file);

// 分配内存
*students = (Student *)malloc(*count * sizeof(Student));
if (*students == NULL) {
printf("内存分配失败\n");
fclose(file);
return;
}

// 读取学生信息
fread(*students, sizeof(Student), *count, file);
fclose(file);
printf("学生信息已从文件加载\n");
}

总结

C语言中的结构体和联合体是强大的数据结构,通过本文档的学习,我们了解了:

  1. 结构体的基本概念:结构体是一种用户自定义的数据类型,可以包含不同类型的成员变量
  2. 结构体的定义与初始化:包括按顺序初始化和指定初始化器
  3. 结构体数组:存储多个结构体的数组
  4. 结构体指针:指向结构体的指针,使用箭头运算符访问成员
  5. 嵌套结构体:在结构体中包含另一个结构体作为成员
  6. 位域结构体:使用位域节省内存空间
  7. 联合体:所有成员共享同一块内存空间的特殊数据类型
  8. 枚举与结构体:枚举作为结构体成员的使用方法
  9. 结构体作为函数参数:值传递和指针传递的区别
  10. 结构体的高级应用:链表实现、文件I/O等
  11. 最佳实践:使用typedef简化声明、合理设计结构体成员、初始化结构体等

结构体和联合体在C语言中有着广泛的应用,它们可以帮助我们更好地组织和管理数据,提高代码的可读性和可维护性。通过合理使用结构体和联合体,我们可以编写出更加高效、清晰的C程序。

structs_unions.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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
#include <stdio.h>
#include <string.h>

/**
* @brief 结构体和联合体示例程序
* @details 这个程序演示了C语言中结构体和联合体的各种用法,包括
* 结构体定义、初始化、结构体数组、结构体指针、嵌套结构体、
* 位域结构体以及联合体的定义和使用。
* @return 0 表示程序成功执行
*/

// =========================
// 结构体定义
// =========================

/**
* @brief 学生结构体
* @details 包含学生的基本信息:姓名、年龄、学号和成绩
*/
struct Student {
char name[50]; // 姓名
int age; // 年龄
int student_id; // 学号
float score; // 成绩
};

/**
* @brief 日期结构体
* @details 包含年、月、日信息
*/
struct Date {
int year;
int month;
int day;
};

/**
* @brief 员工结构体(嵌套结构体示例)
* @details 包含员工基本信息和入职日期
*/
struct Employee {
char name[50];
int employee_id;
float salary;
struct Date hire_date; // 嵌套日期结构体
};

/**
* @brief 位域结构体示例
* @details 使用位域来节省内存空间
*/
struct Flags {
unsigned int is_active : 1; // 1位:是否激活
unsigned int is_admin : 1; // 1位:是否管理员
unsigned int user_type : 2; // 2位:用户类型(0-3)
unsigned int permissions : 4; // 4位:权限标志
};

/**
* @brief 联合体示例
* @details 联合体的所有成员共享同一块内存空间
*/
union Data {
int i; // 整型
float f; // 浮点型
char str[20]; // 字符数组
};

/**
* @brief 枚举类型示例
* @details 与结构体结合使用的枚举类型
*/
enum Color {
RED, // 0
GREEN, // 1
BLUE, // 2
YELLOW, // 3
BLACK // 4
};

/**
* @brief 带有枚举成员的结构体
*/
struct Shape {
enum Color color;
float area;
char name[20];
};

// =========================
// 函数原型声明
// =========================
void print_student(struct Student s); // 打印学生信息
void update_student_score(struct Student *s, float new_score); // 更新学生成绩
void print_employee(struct Employee e); // 打印员工信息
void print_flags(struct Flags f); // 打印位域标志
void print_union(union Data d); // 打印联合体数据

/**
* @brief 主函数
* @return 0 表示程序成功执行
*/
int main() {
// =========================
// 结构体的基本使用
// =========================
printf("=== 结构体的基本使用 ===\n");

// 结构体变量声明
struct Student student1;

// 结构体成员赋值
strcpy(student1.name, "张三");
student1.age = 20;
student1.student_id = 1001;
student1.score = 95.5;

// 结构体变量初始化
struct Student student2 = {
"李四",
21,
1002,
88.0
};

// 使用指定初始化器(C99及以上)
struct Student student3 = {
.name = "王五",
.student_id = 1003,
.score = 92.5,
.age = 22
};

// 打印学生信息
printf("学生1信息:\n");
print_student(student1);

printf("\n学生2信息:\n");
print_student(student2);

printf("\n学生3信息:\n");
print_student(student3);

printf("\n");

// =========================
// 结构体数组
// =========================
printf("=== 结构体数组 ===\n");

// 结构体数组初始化
struct Student students[] = {
{"赵六", 20, 1004, 85.5},
{"孙七", 21, 1005, 90.0},
{"周八", 22, 1006, 78.5}
};

int num_students = sizeof(students) / sizeof(students[0]);

printf("结构体数组中的学生信息:\n");
for (int i = 0; i < num_students; i++) {
printf("\n学生 %d:\n", i + 1);
print_student(students[i]);
}

printf("\n");

// =========================
// 结构体指针
// =========================
printf("=== 结构体指针 ===\n");

// 结构体指针声明和初始化
struct Student *student_ptr = &student1;

// 通过指针访问结构体成员(两种方式)
printf("通过指针访问学生1信息:\n");
printf("姓名:%s\n", student_ptr->name); // 箭头运算符
printf("年龄:%d\n", (*student_ptr).age); // 解引用后使用点运算符

// 使用结构体指针更新成员
update_student_score(student_ptr, 98.0);
printf("更新后学生1成绩:%.1f\n", student1.score);

printf("\n");

// =========================
// 嵌套结构体
// =========================
printf("=== 嵌套结构体 ===\n");

// 初始化嵌套结构体
struct Employee emp = {
"王五",
2001,
5000.0,
{2020, 3, 15}
};

// 打印员工信息
print_employee(emp);

// 修改嵌套结构体成员
emp.hire_date.year = 2021;
printf("\n修改入职年份后:\n");
print_employee(emp);

printf("\n");

// =========================
// 位域结构体
// =========================
printf("=== 位域结构体 ===\n");

// 初始化位域结构体
struct Flags flags = {
1, // is_active = true
0, // is_admin = false
2, // user_type = 2
10 // permissions = 10 (二进制:1010)
};

print_flags(flags);

// 修改位域成员
flags.is_admin = 1;
printf("\n修改为管理员后:\n");
print_flags(flags);

// 显示位域结构体的大小
printf("\n位域结构体大小:%zu 字节\n", sizeof(struct Flags));

printf("\n");

// =========================
// 联合体
// =========================
printf("=== 联合体 ===\n");

// 联合体变量声明和初始化
union Data data;

// 赋值为整型
data.i = 123;
printf("联合体存储整型:\n");
print_union(data);

// 赋值为浮点型(会覆盖之前的整型值)
data.f = 3.14159;
printf("\n联合体存储浮点型:\n");
print_union(data);

// 赋值为字符串(会覆盖之前的浮点型值)
strcpy(data.str, "Hello, Union!");
printf("\n联合体存储字符串:\n");
print_union(data);

// 显示联合体的大小
printf("\n联合体大小:%zu 字节\n", sizeof(union Data));

printf("\n");

// =========================
// 枚举与结构体结合使用
// =========================
printf("=== 枚举与结构体结合使用 ===\n");

// 初始化带有枚举成员的结构体
struct Shape circle = {
RED,
78.5,
"Circle"
};

struct Shape rectangle = {
BLUE,
120.0,
"Rectangle"
};

printf("图形1:\n");
printf("名称:%s\n", circle.name);
printf("颜色:%d\n", circle.color); // 枚举值为整数
printf("面积:%.1f\n", circle.area);

printf("\n图形2:\n");
printf("名称:%s\n", rectangle.name);
printf("颜色:%d\n", rectangle.color);
printf("面积:%.1f\n", rectangle.area);

printf("\n");

// =========================
// 结构体作为函数参数
// =========================
printf("=== 结构体作为函数参数 ===\n");

// 通过值传递结构体
printf("通过值传递结构体:\n");
print_student(student1);

// 通过指针传递结构体(更高效,尤其是大型结构体)
printf("\n通过指针传递结构体更新成绩:\n");
printf("更新前成绩:%.1f\n", student1.score);
update_student_score(&student1, 99.5);
printf("更新后成绩:%.1f\n", student1.score);

return 0;
}

// =========================
// 函数定义
// =========================

/**
* @brief 打印学生信息
* @param s 学生结构体(值传递)
*/
void print_student(struct Student s) {
printf("姓名:%s\n", s.name);
printf("年龄:%d\n", s.age);
printf("学号:%d\n", s.student_id);
printf("成绩:%.1f\n", s.score);
}

/**
* @brief 更新学生成绩
* @param s 学生结构体指针(指针传递)
* @param new_score 新的成绩
*/
void update_student_score(struct Student *s, float new_score) {
s->score = new_score;
}

/**
* @brief 打印员工信息
* @param e 员工结构体
*/
void print_employee(struct Employee e) {
printf("姓名:%s\n", e.name);
printf("员工ID:%d\n", e.employee_id);
printf("工资:%.2f\n", e.salary);
printf("入职日期:%d-%d-%d\n", e.hire_date.year, e.hire_date.month, e.hire_date.day);
}

/**
* @brief 打印位域标志信息
* @param f 位域结构体
*/
void print_flags(struct Flags f) {
printf("is_active:%d\n", f.is_active);
printf("is_admin:%d\n", f.is_admin);
printf("user_type:%d\n", f.user_type);
printf("permissions:%d\n", f.permissions);
}

/**
* @brief 打印联合体数据
* @param d 联合体
* @note 由于联合体成员共享内存,只有最后赋值的成员值是有效的
*/
void print_union(union Data d) {
printf("整型值:%d\n", d.i);
printf("浮点值:%.2f\n", d.f);
printf("字符串:%s\n", d.str);
}