引言
在 C 语言中,结构体是一种非常强大的数据类型,用于组织不同类型的数据成员。通过结构体,我们可以创建复杂的数据结构,用于表示现实生活中的对象。本文将详细介绍 C 语言中结构体的基本概念、语法、使用方法以及一些高级主题,包括底层原理和具体应用。
结构体基础知识
定义结构体
结构体通过关键字 struct
来定义。结构体中可以包含不同类型的成员变量,这些成员可以是基本类型(如 int
, float
等)也可以是其他结构体类型。
struct student {
char name[50]; // 学生姓名
int age; // 年龄
float gpa; // 平均成绩点数
};
使用结构体
定义结构体后,可以通过结构体变量来使用它。
struct student s1;
strcpy(s1.name, "Alice");
s1.age = 20;
s1.gpa = 3.5;
访问结构体成员
可以通过.
操作符来访问结构体成员。
printf("Name: %s, Age: %d, GPA: %.2f\n", s1.name, s1.age, s1.gpa);
结构体的高级用法
匿名结构体
匿名结构体没有名称,主要用于在定义变量的同时初始化结构体。
struct {
char name[50]; // 学生姓名
int age; // 年龄
float gpa; // 平均成绩点数
} s2 = {"Bob", 22, 3.7};
结构体数组
可以创建结构体数组来存储多个结构体实例。
struct student students[3] = {
{"Alice", 20, 3.5},
{"Bob", 22, 3.7},
{"Charlie", 21, 3.8}
};
for (int i = 0; i < 3; i++) {
printf("Student %d: Name: %s, Age: %d, GPA: %.2f\n",
i + 1, students[i].name, students[i].age, students[i].gpa);
}
结构体指针
可以定义指向结构体的指针,并通过指针来访问结构体成员。
struct student *ptr = &s1;
printf("Pointer to structure: Name: %s, Age: %d, GPA: %.2f\n",
ptr->name, ptr->age, ptr->gpa);
结构体与函数
可以将结构体作为参数传递给函数,也可以返回结构体。
struct student get_student_info() {
struct student s;
strcpy(s.name, "David");
s.age = 23;
s.gpa = 3.6;
return s;
}
int main() {
struct student s3 = get_student_info();
printf("Returned structure: Name: %s, Age: %d, GPA: %.2f\n",
s3.name, s3.age, s3.gpa);
return 0;
}
结构体的内存布局
字节对齐
为了提高性能,编译器会对结构体中的成员进行字节对齐。这意味着成员的地址通常是其类型大小的倍数。例如,int
类型在许多系统上是 4 字节,那么 int
成员的地址通常是 4 的倍数。
struct example {
char c; // 1 字节
int i; // 4 字节
double d; // 8 字节
};
在上面的例子中,char c
之后会有填充(padding),以便 int i
的地址是 4 的倍数。同样,int i
之后也会有填充,以便 double d
的地址是 8 的倍数。
结构体的大小
结构体的总大小等于所有成员的大小加上必要的填充。
struct example {
char c; // 1 字节
int i; // 4 字节
double d; // 8 字节
};
printf("Size of struct example: %zu\n", sizeof(struct example));
包含结构体的结构体
可以将一个结构体作为另一个结构体的成员。
struct person {
char name[50]; // 姓名
int age; // 年龄
};
struct employee {
struct person info; // 个人信息
double salary; // 工资
};
struct employee emp = {{"John Doe", 30}, 50000.0};
结构体与联合
联合
联合是一种特殊的数据类型,它允许多个成员共享同一段内存空间。这意味着联合的大小等于最大成员的大小。
union mixed {
int i; // 整数
double d; // 浮点数
char str[20]; // 字符串
};
union mixed m;
m.i = 42;
m.d = 3.14;
strcpy(m.str, "Hello, union!");
printf("Integer: %d\n", m.i);
printf("Double: %.2f\n", m.d);
printf("String: %s\n", m.str);
结构体与联合的混合使用
可以在结构体中使用联合来节省内存。
struct complex {
char name[50]; // 姓名
union {
int age; // 年龄
double salary;// 工资
} u;
};
struct complex c = {{"Jane Smith"}, .u = {35}};
printf("Name: %s, Age: %d\n", c.name, c.u.age);
结构体与枚举
枚举类型
枚举是一种特殊的类型,用于定义一组命名的常量。
enum day {
MONDAY, // 星期一
TUESDAY, // 星期二
WEDNESDAY, // 星期三
THURSDAY, // 星期四
FRIDAY, // 星期五
SATURDAY, // 星期六
SUNDAY // 星期天
};
struct week {
enum day today; // 今天
enum day tomorrow; // 明天
};
struct week w = {{MONDAY}, {TUESDAY}};
printf("Today: %d, Tomorrow: %d\n", w.today, w.tomorrow);
结构体与位字段
位字段
位字段允许在结构体中定义具有特定位数的成员。这对于存储标志或控制位特别有用。
struct flags {
unsigned int read_only : 1; // 只读标志
unsigned int hidden : 1; // 隐藏标志
unsigned int executable : 1; // 可执行标志
unsigned int reserved : 1; // 保留位
unsigned int unused : 28; // 未使用的位
};
struct flags f = {1, 1, 1, 0, 0};
printf("Flags: read_only=%d, hidden=%d, executable=%d\n",
f.read_only, f.hidden, f.executable);
结构体与动态分配
动态分配结构体
可以使用动态内存分配函数(如 malloc
和 calloc
)来分配结构体的内存。
struct student *dynamic_student = (struct student *)malloc(sizeof(struct student));
strcpy(dynamic_student->name, "Eva");
dynamic_student->age = 24;
dynamic_student->gpa = 3.9;
printf("Dynamic Student: Name: %s, Age: %d, GPA: %.2f\n",
dynamic_student->name, dynamic_student->age, dynamic_student->gpa);
free(dynamic_student);
动态分配结构体数组
还可以动态分配结构体数组的内存。
struct student *students = (struct student *)malloc(3 * sizeof(struct student));
for (int i = 0; i < 3; i++) {
students[i].age = i + 20;
students[i].gpa = 3.0 + i * 0.5;
}
for (int i = 0; i < 3; i++) {
printf("Student %d: Age: %d, GPA: %.2f\n", i + 1, students[i].age, students[i].gpa);
}
free(students);
结构体与文件操作
结构体的序列化与反序列化
可以使用标准 I/O 函数(如 fwrite
和 fread
)来保存和恢复结构体的内容。
struct student s4 = {"Michael", 25, 3.8};
FILE *fp = fopen("students.bin", "wb");
if (fp == NULL) {
perror("Error opening file");
exit(EXIT_FAILURE);
}
fwrite(&s4, sizeof(struct student), 1, fp);
fclose(fp);
// 读取结构体
FILE *fp2 = fopen("students.bin", "rb");
if (fp2 == NULL) {
perror("Error opening file");
exit(EXIT_FAILURE);
}
struct student s5;
fread(&s5, sizeof(struct student), 1, fp2);
fclose(fp2);
printf("Loaded Student: Name: %s, Age: %d, GPA: %.2f\n",
s5.name, s5.age, s5.gpa);
结构体与内存布局详解
字节对齐的细节
字节对齐是为了提高数据访问效率而设计的。编译器通常会根据目标平台的对齐要求来自动插入填充字节。
struct example {
char c; // 1 字节
short int s; // 2 字节
int i; // 4 字节
double d; // 8 字节
};
printf("Size of struct example: %zu\n", sizeof(struct example));
在上述例子中,char c
后面可能会有一个填充字节,使 short int s
的地址成为 2 的倍数。接着,int i
之前可能会有两个填充字节,使其地址成为 4 的倍数。最后,double d
之前可能会有四个填充字节,使其地址成为 8 的倍数。因此,最终的结构体大小可能比成员总和大。
字节对齐的影响
字节对齐会影响结构体的内存使用效率。如果不需要严格的对齐要求,可以使用 #pragma pack
指令来控制对齐行为。
#pragma pack(push, 1)
struct packed_example {
char c; // 1 字节
short int s; // 2 字节
int i; // 4 字节
double d; // 8 字节
};
#pragma pack(pop)
printf("Size of struct packed_example: %zu\n", sizeof(struct packed_example));
在上面的例子中,使用了 #pragma pack
指令来禁用对齐填充,这样结构体的大小就等于成员的总和。
字节对齐的平台差异
不同平台上的对齐规则可能会有所不同。例如,在某些 32 位系统上,double
类型可能只需要 4 字节的对齐,而在 64 位系统上则需要 8 字节的对齐。编写跨平台代码时需要考虑这一点。
结构体与内存访问优化
数据访问模式
当结构体中的成员被频繁访问时,合理的布局可以减少缓存缺失,从而提高性能。
struct student {
char name[50]; // 姓名
int age; // 年龄
float gpa; // 平均成绩点数
};
struct student students[100000]; // 存储大量学生信息
// 假设我们要查找所有年龄大于某个值的学生
int threshold = 25;
int count = 0;
for (int i = 0; i < 100000; i++) {
if (students[i].age > threshold) {
count++;
}
}
printf("Number of students older than %d: %d\n", threshold, count);
缓存友好布局
在上述例子中,如果访问模式是连续的,那么可以通过将最常访问的成员放在前面来优化缓存使用。
struct student {
int age; // 年龄
char name[50]; // 姓名
float gpa; // 平均成绩点数
};
struct student students[100000]; // 存储大量学生信息
int threshold = 25;
int count = 0;
for (int i = 0; i < 100000; i++) {
if (students[i].age > threshold) {
count++;
}
}
printf("Number of students older than %d: %d\n", threshold, count);
结构体与内存对齐优化
对齐与性能
某些处理器架构对未对齐的内存访问有较高的性能开销。例如,如果在一个 64 位平台上访问一个未对齐的 64 位整数,处理器可能会需要两次内存访问才能读取完整数据。
struct misaligned {
int i; // 4 字节
double d; // 8 字节
};
struct aligned {
double d; // 8 字节
int i; // 4 字节
};
struct misaligned m;
struct aligned a;
// 假设我们要读取结构体中的双精度浮点数
double value_m = m.d;
double value_a = a.d;
// 在某些平台上,读取 `value_m` 可能会导致未对齐访问
对齐优化工具
现代编译器提供了多种工具和技术来帮助优化对齐。例如,GCC 提供了 __attribute__((aligned))
属性来显式指定对齐要求。
struct aligned {
double d __attribute__((aligned(8))); // 8 字节对齐
int i;
};
struct aligned a;
double value_a = a.d;
结构体与内存管理
动态结构体的生命周期
动态分配的结构体需要手动管理其生命周期。当不再需要结构体时,应该释放其占用的内存。
struct student *dynamic_student = (struct student *)malloc(sizeof(struct student));
strcpy(dynamic_student->name, "Eva");
dynamic_student->age = 24;
dynamic_student->gpa = 3.9;
// 使用完后释放内存
free(dynamic_student);
结构体数组的生命周期
动态分配的结构体数组也需要手动管理内存。
struct student *students = (struct student *)malloc(3 * sizeof(struct student));
for (int i = 0; i < 3; i++) {
students[i].age = i + 20;
students[i].gpa = 3.0 + i * 0.5;
}
// 使用完后释放内存
free(students);
结构体与内存泄漏
如果不正确管理结构体的生命周期,可能会导致内存泄漏。务必确保释放所有动态分配的内存。
struct student *students = (struct student *)malloc(3 * sizeof(struct student));
// 忘记释放内存
// free(students);
结构体与复杂数据结构
结构体与链表
结构体可以用来实现链表这样的复杂数据结构。
struct node {
int data; // 数据
struct node *next; // 下一个节点
};
struct node *head = NULL;
void append(int data) {
struct node *new_node = (struct node *)malloc(sizeof(struct node));
new_node->data = data;
new_node->next = NULL;
if (head == NULL) {
head = new_node;
} else {
struct node *current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = new_node;
}
}
void print_list() {
struct node *current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
int main() {
append(1);
append(2);
append(3);
print_list();
return 0;
}
结构体与树
结构体也可以用来实现树形结构。
struct tree_node {
int data; // 数据
struct tree_node *left; // 左子树
struct tree_node *right; // 右子树
};
struct tree_node *root = NULL;
struct tree_node *create_node(int data) {
struct tree_node *new_node = (struct tree_node *)malloc(sizeof(struct tree_node));
new_node->data = data;
new_node->left = NULL;
new_node->right = NULL;
return new_node;
}
void insert(int data) {
if (root == NULL) {
root = create_node(data);
} else {
struct tree_node *current = root;
struct tree_node *parent = NULL;
while (current != NULL) {
parent = current;
if (data < current->data) {
current = current->left;
} else {
current = current->right;
}
}
if (data < parent->data) {
parent->left = create_node(data);
} else {
parent->right = create_node(data);
}
}
}
void print_tree_inorder(struct tree_node *node) {
if (node != NULL) {
print_tree_inorder(node->left);
printf("%d ", node->data);
print_tree_inorder(node->right);
}
}
int main() {
insert(5);
insert(3);
insert(7);
insert(1);
insert(4);
insert(6);
insert(8);
print_tree_inorder(root);
return 0;
}
总结
本文详细介绍了 C 语言中的结构体,包括基本用法、高级用法、内存布局、联合、枚举、位字段以及动态分配等内容。通过学习这些知识,您可以更加灵活地使用结构体来构建复杂的数据结构。