跳转至

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 把逻辑顺序串起来:

struct node {
    int data;
    struct node *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

联合体的成员共用同一块内存,写入一个成员会覆盖原来的内容。它的大小至少要装得下最大的那个成员:

union value {
    int i;
    double d;
    char text[20];
};

所以同一时刻通常只认最后写入的成员。实际使用时常再配一个枚举,专门记录“现在存的到底是哪一种数据”。

enum color { RED, GREEN, BLUE };
typedef struct node Node;

枚举常量默认从 0 开始,也可以自己指定数值。typedef 只是给已有类型换个短一点的名字,并没有创造一种全新的类型。

评论