11. 结构体、联合体、枚举与链表
11.1 结构体
数组要求所有元素同类型,而结构体正好用来把不同类型的数据捆在一起。例如一个日期同时有年、月、日:
struct date {
int year;
int month;
int day;
};
struct date today = {2026, 7, 14};
printf("%d-%02d-%02d\n", today.year, today.month, today.day);
. 跟在结构体对象后面,-> 跟在结构体指针后面,p->member 就是 (*p).member。同类型结构体能整体赋值,也能作为函数参数和返回值;但 C 没有定义整个结构体的 ==,比较时还是要看成员。
结构体按值传参会复制全部成员。结构体很大时通常传指针:只读就加 const,要修改就传普通结构体指针。
结构体成员本身还可以是结构体,也可以定义结构体数组。例如学生数组中的每个元素都是一份完整的学生记录。数组传给函数时仍按首元素地址处理,而单个结构体传参会把整个结构体复制一份,这两种情况不要混。
11.2 对齐与结构体大小
sizeof(结构体) 经常比各成员大小简单相加更大,因为编译器会在成员之间塞入填充字节来满足对齐。成员顺序一换,结构体总大小也可能跟着变。
#pragma pack 可以调整某些编译器的对齐规则。不过结构体直接写进二进制文件后,仍然可能受到字节序、类型宽度和浮点表示影响,换一台机器未必还能原样读。
11.3 单链表
单链表的结点分成数据域和指针域。结点在内存中可以东一块西一块,全靠 next 把逻辑顺序串起来:
头指针为 NULL 表示空表。头插法建立链表:
struct node *push_front(struct node *head, int value)
{
struct node *p = malloc(sizeof *p);
if (p == NULL) return head;
p->data = value;
p->next = head;
return p;
}
遍历就是从头指针开始不断走 next,直到 NULL。链表题真正容易错的不是循环,而是改指针的顺序:旧的后继还没保存时不要先把链接断掉,free(p) 以后也不能再读 p->next。
void clear(struct node **head)
{
struct node *p = *head;
while (p != NULL) {
struct node *next = p->next;
free(p);
p = next;
}
*head = NULL;
}
要频繁按下标访问就偏向数组;已经拿到结点位置、又要经常插删时,链表更顺手。课件还用链表表示多项式:每个结点保存系数、指数和下一项指针,相加时按指数合并同类项。
11.4 联合体、枚举与 typedef
联合体的成员共用同一块内存,写入一个成员会覆盖原来的内容。它的大小至少要装得下最大的那个成员:
所以同一时刻通常只认最后写入的成员。实际使用时常再配一个枚举,专门记录“现在存的到底是哪一种数据”。
枚举常量默认从 0 开始,也可以自己指定数值。typedef 只是给已有类型换个短一点的名字,并没有创造一种全新的类型。