C语言里的“百变星君”:结构体与联合体
C 语言的基石:结构体

在 C 语言的世界里,结构体(struct)就像是一个功能强大的 “收纳盒”,它允许我们把不同类型的数据整合在一起,形成一个有机的整体,是 C 语言中极为重要的自定义数据类型,也是数据处理的有力工具。
结构体基本概念与定义
结构体是一种用户自定义的数据类型,它可以将多个不同类型的变量组合成一个逻辑单元。比如说,我们想要描述一个学生的信息,可能会涉及到学号、姓名、年龄、成绩等,这些数据的类型各不相同,学号可以是整数,姓名是字符串,年龄是整数,成绩是浮点数。使用结构体,我们就能轻松地把这些信息整合在一起。定义结构体的语法如下:
struct 结构体名 {
数据类型 成员1;
数据类型 成员2;
// 可以有更多成员
};
例如,定义一个学生结构体:
struct Student {
int id; // 学号
char name[20]; // 姓名
int age; // 年龄
float score; // 成绩
};
这里我们定义了一个名为Student
的结构体,它包含了id
、name
、age
和score
四个成员,分别表示学号、姓名、年龄和成绩。
定义结构体变量后,可以通过点号(.)操作符来访问结构体的成员,比如:
struct Student stu;
stu.id = 1;
strcpy(stu.name, "Alice");
stu.age = 20;
stu.score = 85.5;
内存布局与对齐规则
结构体的内存分配并不是简单地将所有成员的内存大小相加,而是要遵循内存对齐(Alignment)规则。内存对齐的目的主要是为了提高内存访问的效率,因为处理器在访问内存时,通常更倾向于访问特定对齐方式的数据。
对齐规则如下:
每个成员的起始地址必须是其自身大小的整数倍。例如,int
类型通常占 4 字节,它的起始地址应该是 4 的倍数;double
类型占 8 字节,它的起始地址应该是 8 的倍数。
结构体的总大小必须是其最大成员大小的整数倍。
以一个具体的结构体为例:
struct Data {
char a; // 1字节
int b; // 4字节
double c; // 8字节
};
在 32 位系统下,char
类型占 1 字节,int
类型占 4 字节,double
类型占 8 字节。按照内存对齐规则,a
占用 1 字节,由于b
是int
类型,需要 4 字节对齐,所以在a
后面会填充 3 个字节的空隙,b
从第 4 个字节开始存储;c
是double
类型,需要 8 字节对齐,所以c
从第 8 个字节开始存储。那么这个结构体的总大小就是 1 + 3(填充)+ 4 + 8 = 16 字节。
我们也可以通过#pragma pack(n)
指令来调整对齐系数,例如#pragma pack(1)
表示取消对齐,让结构体成员紧密排列,这样可以节省内存空间,但可能会影响性能,因为不符合处理器的内存访问习惯。
结构体应用场景
「数据封装」:这是结构体最常见的应用场景之一。比如在学生信息管理系统中,可以用结构体来封装每个学生的信息,将学号、姓名、年龄、成绩等信息整合在一起,方便对学生信息进行统一管理和操作。在网络编程中,网络协议头也可以用结构体来表示,将 IP 地址、端口号、协议类型等信息封装在一起,便于数据的传输和解析。
「函数参数传递」:当函数需要传递多个参数时,如果这些参数之间存在逻辑关联,使用结构体可以将它们组合成一个参数,使函数接口更加简洁明了。例如,一个计算矩形面积的函数,可以接收一个包含矩形长和宽的结构体作为参数,而不是分别传递长和宽两个参数。
「链表节点构建」:在链表这种数据结构中,每个节点通常包含数据和指向下一个节点的指针。使用结构体可以很方便地定义链表节点,例如:
struct Node {
int data;
struct Node* next;
};
这里Node
结构体包含一个data
成员用于存储数据,一个next
指针用于指向下一个节点,通过这种方式可以构建出复杂的链表结构。
初始化与赋值
「初始化」:结构体可以在定义时进行初始化,有多种初始化方式。
「顺序初始化」:按照结构体成员的定义顺序进行初始化,例如: