当一堆相关数据想「住在一起」——比如一个学生要有姓名、年龄、分数——把它们打包放进同一座小房子里,就会舒服很多。这座小房子,就叫结构体(struct)。
房子的初样:声明与自引用
struct Stu {
char name[20];
int age;
float score;
};
// 自引用:链表节点指向下一个同类节点
struct Node {
int data;
struct Node* next; // 一定是"指针",不能是 struct Node next
};
还有「匿名结构体」——不写名字,用完即弃,之后就不能再用这个类型创建新变量了。
内存对齐:房子也要"工整"
看一个有点反直觉的现象:
struct S1 { char c1; char c2; int n; }; // 8 字节
struct S2 { char c1; int n; char c2; }; // 12 字节 —— 同样的成员,顺序不同大小不同!
原因就是内存对齐。规则有四条:
- 第一个成员放在偏移量为 0 处。
- 其他成员要对齐到「对齐数」的整数倍地址;对齐数 = 编译器默认值(VS 为 8)与该成员大小的较小值。
- 结构体总大小 = 最大对齐数的整数倍。
- 嵌套结构体要对齐到自己成员中最大对齐数的整数倍。
以 S1 为例:c1 放 0,c2 放 1,n(对齐数 4)放 4~7,总共 8 字节。而 S2 里 n 被迫从 4 开始,c2 落在 8,总共 9,再补齐到最大对齐数 4 的倍数 → 12。
为什么要对齐?用空间换时间
一是平台原因:有些硬件只能在特定地址存取特定类型,否则会抛异常。二是性能原因:对齐的访问只要一次内存操作,未对齐可能要两次。所以内存对齐本质是拿一点点空间换更快的速度。
想省空间也有小技巧:让占用小的成员尽量挤在一起(就像 S1 那样)。还可用 #pragma pack(1) 修改默认对齐数(一般设成 2 的次方)。
传参:把地址交出去
void print1(struct S s); // 传整个结构体:要压栈拷贝,太慢
void print2(struct S* ps); // 传地址:轻快 ✅
结构体过大时,传值会带来沉重的拷贝开销,所以结构体传参首选传地址。
位段:按"位"精打细算
struct A {
int _a : 2; // 只占 2 个比特位
int _b : 5;
int _c : 10;
int _d : 30;
}; // sizeof = 8(而普通 int×4 是 16)
位段能狠狠节省空间,但它不跨平台——成员从哪边分配、超出剩余位时舍弃还是利用,标准并未定义。而且位段成员没有地址,不能用 & 取地址,也就不能直接 scanf。
✿ 摘要
- 结构体把相关数据打包成一种新类型,可自引用来实现链表。
- 内存对齐:成员按「对齐数」整数倍摆放,全部大小凑成最大对齐数的倍数。
- 对齐是为了性能与平台兼容,本质用空间换时间;小成员放一起更省空间。
- 结构体传参优先传地址,避免大拷贝。
- 位段按 bit 省空间,但不跨平台、不能取地址。
愿当下的花如你所见般美丽
评论交流
欢迎留下你的想法