6
结构体、共用体、枚举、typedef
struct · union · enum · typedef
数组只能装"同一类东西"。可现实里一个学生有姓名(字符串)、年龄(int)、成绩(double)——struct 就是把不同类型的数据打包成一个"自定义类型"。这是 C 迈向"组织复杂数据"的第一步。
struct 结构体
定义、初始化、访问成员
#include <stdio.h>
#include <string.h>
// 定义一个"学生"类型
struct Student {
char name[20];
int age;
double score;
};
int main() {
// 初始化
struct Student s = {"小明", 18, 92.5};
printf("%s %d %.1f\n", s.name, s.age, s.score); // 用 . 访问成员
s.age = 19; // 修改
// 结构体指针用 -> 访问成员,等价于 (*p).name
struct Student *p = &s;
printf("名字:%s\n", p->name);
return 0;
}
运行结果
小明 18 92.5
名字:小明
记. 和 -> 的区别
结构体变量用 .:s.name。
结构体指针用 ->:p->name。
其实 p->name 就是 (*p).name 的简写,C 嫌它啰嗦才发明了箭头。记法:手里是实体就点,手里是地址就箭头。
union 共用体:所有人挤同一间房
union 和 struct 长得像,但行为相反:所有成员共享同一块内存,同一时刻只能用一个。它的大小等于最大的那个成员。常见于"这个字段可能是 int 也可能是 float"这类省内存场景。
union 的共享内存特性
union Data {
int i;
float f;
char str[16];
}; // 大小取最大成员 str 的 16 字节
union Data d;
d.i = 65;
printf("%d\n", d.i); // 65
d.f = 3.14f; // 现在 f 和 i 是同一块内存,再读 d.i 就变成无意义的乱数了
enum 枚举与 typedef 别名
枚举给数字起名字;typedef 给类型起别名
enum Color { RED, GREEN, BLUE }; // 实际是 0,1,2,但用名字比数字可读
enum Color c = GREEN;
// typedef 给长类型起个短名字
typedef unsigned long u64; // 以后 u64 就是 unsigned long
// 结构体 + typedef 的经典组合,省得每次写 struct
typedef struct {
int x, y;
} Point;
Point p = {3, 4}; // 不用再写 struct Point
结构体内存对齐与位域
你以为结构体大小是各成员相加?天真。编译器为了 CPU 读取更快,会在成员之间"垫"一些空字节——这就是内存对齐。所以 sizeof(struct) 往往比你手算的大。
对齐实测:把大成员放前面能省空间
typedef struct {
char c; // 1 字节
int i; // 4 字节,但要对齐到 4 的倍数:c 后面会被垫 3 字节
char d; // 1 字节
} Bad; // sizeof = 12 而不是 6!
typedef struct {
int i; // 4 字节先放
char c; // 1 字节
char d; // 1 字节,和 c 挤一起
} Good; // sizeof = 8
// 口诀:按成员大小从大到小排,省垫补。offsetof(struct, member) 可量出偏移。
强制对齐:跨平台通信、硬件寄存器映射时需要紧凑布局,用 #pragma pack(1) 告诉编译器"别垫字节,按 1 字节对齐":
#pragma pack(push, 1)
typedef struct {
char cmd;
int len;
} Packet; // sizeof 严格按 1+4=5,不垫补
#pragma pack(pop) // 恢复之前的对齐设置
位域(bit-field):连字节都嫌多,想直接用"几个 bit"。适合标志位、协议头:
struct Flags {
unsigned int ready : 1; // 只占 1 个 bit
unsigned int mode : 3; // 占 3 个 bit(0~7)
unsigned int err : 4; // 占 4 个 bit
}; // 总共 8 bit = 1 字节
struct Flags f;
f.ready = 1; // 直接当成员用,编译器帮你位运算