结构体
结构体是一些值的合集,这些值称为成员变量
成员变量可以是标量、数组、指针甚至是其他结构体
结构体的声明
1 2 3 4
| struct tag { member-list; };variable - list;
|
假如要描述一个学生
1 2 3 4 5 6 7
| struct Stu { char name[20]; int age; char sex[5]; char id[20]; };
|
结构体变量的初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| struct Stu { char name[20]; int age; char sex[5]; char id[20]; };
struct Stu stu1 = { "张三",18,"male","23001" }; struct Stu stu2 = { .name = "李四",.age = 19,.sex = "fmale",.id ="23002"};
struct Book { char name[20]; char author[20]; float price; }book1 = {"三国演义","罗贯中",6.66};
struct Book book2 = { "西游记","吴承恩",6.66 };
struct Test { int num; struct Book; };
struct Test test1 = { 1,{"红楼梦","曹雪芹",6.66} };
|
结构体的特殊声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct { int a; int b; int c; }x;
struct { char a; char b; char c; }c = {'a','b','c'};
|
上面两端代码在声明时省略了结构体标签
需要注意的是编译器会将多个匿名结构体当成完全不同的类型
匿名结构体如果没有对结构体类型重命名,基本上只能用一次

结构体访问操作符
结构体成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的
1 2 3 4 5 6 7 8 9 10 11 12
| struct Point { int x; int y; }p = { 1,2 };
int main() { printf("%d %d", p.x, p.y); return 0; }
|

结构体成员的间接访问
结构体成员的间接访问是通过点操作符(->)访问的
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct Point { int x; int y; }p = { 1,2 };
int main() { struct Point* ps = &p; printf("%d %d\n", ps->x, ps->y); printf("%d %d", p.x, p.y); return 0; }
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct Student { char name[15]; int age; };
void print_stu_1(struct Student s) { printf("%s %d\n", s.name, s.age); }
void print_stu_2(struct Student* s) { printf("%s %d\n", s->name,s->age); }
int main() { struct Student stu1 = { "张三",18 }; struct Student* pstu1 = &stu1 ; print_stu_1(stu1); print_stu_2(pstu1); return 0; }
|

结构体的内存对齐
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct Test { int x; char a; int y; };
int main() { struct Test t1 = { 1,'a',2 }; printf("%zd\n", sizeof(t1)); return 0; }
|

按理来说两个 int 和 一个 char 只用 6 个字节就可以了,这里为什么分配了 12 个
字节
结构体的内存对齐规则

那么上述代码就是这么分配内存空间的
首先这是一段内存,一个方块表示一个字节

int 类型变量 x 从 0 处开始存放,存放四个字节

在存放 char 类型变量 a 时 , 需要先确定对齐数,对齐数 = 编译器默认对齐数与成
员变量大小的较小值, 在 visual studio 2026 中默认对齐数是 8 , char 类型是 1 , 此
处使用的对齐数是 1 , 对齐到 1 的整数倍开始存

同理,可推导出如何存 y

而结构体总大小为最大对其数的整数倍,这个结构体中的最大对齐数是 4 ,所以这个结构体的大小就是 12 个字节
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct Test2 { int x; struct Test t; int y; };
int main() { struct Test2 t2 = { 0 }; printf("%zd\n", sizeof(t2)); return 0; }
|

结构体的对齐值由其内部成员的最大对齐值决定, Test 中最大对齐数是 4 , 4 < 8 ,
存放 t 时要从4的整数倍开始存放

为什么存在内存对齐
平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只
能在某些地址处取某些特定类型的数据,否则抛出硬件异常
性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问
未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一
次访问。
假如现在有一个结构体声明
1 2 3 4 5 6
| struct My_str { char a; int x; };
|
假设都是一次性读取四个字节

一次就能得到 a 或者 x

未对齐的情况下需要读两次, 将 x 拼接出来
进行内存对齐实际上就是用空
间换时间的做法
如果想要节省空间,可以将占空间小的成员变量尽量排在一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct Test3 { char a; char b; int x; };
struct Test4 { char a; int x; char b; };
struct Test3 t3 = { 0 }; struct Test4 t4 = { 0 }; printf("%zd\n", sizeof(t3)); printf("%zd\n", sizeof(t4));
|

修改默认对齐数
在 visual studio 2026 中,可以自行修改默认对齐数
结构体传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| struct S { int data[1000]; int num; };
void print1(struct S s) { printf("%d\n", s.num); }
void print2(struct S* ps) { printf("%d\n", ps->num); }
int main() { struct S s = { {1,2,3,4}, 1000 }; print1(s); print2(&s); return 0; }
|
print1 和 print2 虽然功能一样,但在 print1 进行操作时,需要再创造一个 struct S s
类型的变量来接收,要造成不小的性能开销
而 print2 只需要一个指针变量大小的空间,性能上要节省不少
结构体传参时,首选传结构体的地址
如果担心传址调用会修改实参的的值使用 const 关键字就好了
1 2 3 4 5
| void print2(const struct S* ps) { printf("%d\n", ps->num); }
|
结构体实现位段
位段的声明和结构是类似的,有两个不同:
位段的内存分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct A { int _a : 2; int _b : 5; int _c : 10; int _d : 30; };
struct S { int a; int b; int c; int d; };
printf("%zd\n", sizeof(struct A)); printf("%zd\n", sizeof(struct S));
|

1 2 3 4 5 6 7 8
| struct S2 { char _a : 3; char _b : 4; char _c : 5; char _d : 4; };
|
位段的空间是按照四个字节(int)或一个字节(char)进行开辟的
分配原理如下:

验证
1
| printf("%zd\n", sizeof(struct S2));
|

位段涉及诸多不确定因素,可移植程序应避免使用位段
位段的应用
这是IP数据报的格式,其中很多属性只需几个 bit 就能描述,使用位段既完成了需
求又节约了空间,网络传输的数据报大小也会较小一些,对网络的畅通是由帮助的

位段使用的注意事项
位段的成员会有共用一个字节的情况,某个成员的起始位置并不是该字节的起始位
置,那么这些位置是没有地址的。内存中每一个字节分配一个地址,一个字节内的
bit 位是没有地址的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct S2 { char _a : 3; char _b : 4; char _c : 5; char _d : 3; };
int main() { struct S2 s2 = { 0 }; scanf("%c", &s2._b); return 0; }
|
