
个人主页 : 流年如夢
专栏 : 《C语言》
文章目录
本篇文章为将学习 结构体声明、变量创建、成员访问、内存对齐、结构体传参、位段 六大核心知识点,全程高能,不容错过!!!
前言
在C语言中,基本数据类型只能描述单一属性,而结构体可以将不同类型的数据组合在一起,用来描述复杂对象(如学生、书籍、员工等),是C语言自定义类型的核心。本篇文章带领大家从结构体基础语法入手,重点攻克内存对齐,并深入分析结构体传参与位段用法,帮大家熟练掌握复杂数据的封装与设计
一.结构体类型的声明
在之前的操作符文章简单了解过结构体,结构是一些值的集合,这些值称为成员变量 ,结构的每个成员可以是不同类型的数据。
1.1普通结构体声明
举个例子(以学生为例):
c
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
1.2结构体变量创建与初始化
根据上面对学生的结构体声明在进行变量创建与初始化,如下所示:
c
#include <stdio.h>
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
int main()
{
struct Stu s = { "张三", 20, "男", "20230818001" };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "女" };
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id : %s\n", s2.id);
return 0;
}
其中,struct Stu s是按顺序初始化 ;而 struct Stu s2是按指定成员初始化 (使用结构体访问操作符 . )
运行结果:

🧐分析 :先定义学生结构体,包含姓名、年龄、性别、学号四个成员;再分别使用顺序初始化与指定成员初始化两种方式;使用 . 操作符访问结构体成员
1.3匿名结构体(结构体的特殊声明)
与普通结构体不同的是,在声明结构的时候,可以不完全的声明,如下所示:
c
//1
struct
{
int a;
char b;
float c;
} x;
//2
struct
{
int a;
char b;
float c;
} a[20], *p;
我们可以看见,匿名结构体没有标签(如struct Stu是有标签Stu);然后我写了两个匿名结构体 1 和 2 ,那么问题来了,x 会等于 *p 吗?
答案是不会的,因为编译器会把上⾯的两个声明当成完全不同的两个类型,所以是非法的
值得注意的是,匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次
1.4结构体自引用
举例(正确的自引用:存指针):
c
struct Node
{
int data;
struct Node* next;
};
🧐分析 :因为结构体内部不能包含自身类型变量 ,否则大小无限;所以正确方式是包含自身类型指针,用于链表、树等结构(数据结构)
二.结构体成员访问操作符
两个结构体成员访问操作符:
- 结构体变量
.成员名 - 结构体指针
->成员名
举个例子:
c
#include <stdio.h>
struct Stu
{
char name[20];
int age;
};
int main()
{
struct Stu s = { "张三", 20 };
struct Stu* ps = &s;
printf("%s\n", s.name);
printf("%d\n", ps->age);
return 0;
}
🧐分析 :直接访问结构体变量的时候用 .;通过指针访问的时候用 ->
运行结果:

三.结构体内存对齐(重难点❗)
3.1规则
对齐规则:
- 第一个成员对齐到偏移量为0的位置
- 从第二个成员开始,对齐到对齐数的整数倍
- 结构体总大小是最大对齐数的整数倍
- 嵌套结构体时,嵌套结构体对齐到自己的最大对齐数
其中,对齐数 = 成员大小对比默认对齐数(vs2022=8,gcc = 自身大小)的较小值(我用的是vs2022)
3.2举例
c
#include <stdio.h>
struct S1{char c1;int i;char c2;};
struct S2{char c1;char c2;int i;};
struct S3{double d;char c;int i;};
struct S4{char c1;struct S3 s3;double d;};
int main()
{
printf("%zu\n", sizeof(struct S1));
printf("%zu\n", sizeof(struct S2));
printf("%zu\n", sizeof(struct S3));
printf("%zu\n", sizeof(struct S4));
return 0;
}
🧐分析 :其中 S1 成员分散,内存浪费多需要12 字节;S2 小成员集中,所以比较节省空间需要8 字节;总而言之,结构体内存对齐是用空间换时间
运行结果:

3.3如何修改默认对齐数
我们可以插入#pragma pack(n)手动修改对齐数,当 n = 1 时表示1字节对齐表示不使用对齐,紧凑排布;如下所示:
c
#include <stdio.h>
#pragma pack(1)
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()
int main()
{
printf("%zu\n", sizeof(struct S));
return 0;
}
四.结构体传参
结构体传参有两种:传值、传地址
如下所示:
c
#include <stdio.h>
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4}, 1000 };
//传值
void print1(struct S s)
{
printf("%d\n", s.num);
}
//传地址
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s);
print2(&s);
return 0;
}
🧐分析:传值会完整拷贝结构体,大数据量效率低;传地址仅传指针,效率高,更推荐使用传地址
五.结构体实现位段
5.1什么是位段
➡️位段可以按位分配空间,极大节省内存
5.2位段声明
举例:
c
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
🧐分析 :成员类型必须是整型 , 比如 int、char、unsigned int 等;其中 :数字 表示该成员占多少个 bit 位(例如int _a : 2占了2个bit位)
网上分析位段的内存分配的相关截图:

5.3位段陷阱(注意事项⚠️)
不能直接对位段成员取地址
举个例子:
c
#include <stdio.h>
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = { 0 };
int b = 0;
scanf("%d", &b);//错误写法:scanf("%d", &sa._b);
sa._b = b;
return 0;
}
🧐分析:因为位段成员没有独立地址,所以不能用 & 取地址;赋值需先存入普通变量,再赋值给位段成员
🎯总结
- 结构体是C语言自定义类型,可封装不同类型成员
- 支持顺序初始化、指定成员初始化、匿名结构体、自引用
- 内存对齐是按规则计算大小,用空间换时间
- 结构体传参优先传地址,效率更高
- 位段按 bit 分配空间,节省内存,但不可移植、不可取地址
⚠️易错点
- 结构体声明末尾忘记写分号,编译报错
- 结构体自引用写成
struct Node next; 而非指针- 匿名结构体被编译器视为不同类型,赋值报错
- 结构体内存对齐计算错误
- 结构体大数据传值,导致效率低下
- 对位段成员使用
&取地址,程序出错- 忘记嵌套结构体也参与最大对齐数计算
👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞 鼓励原创,让优质内容被更多人看见
⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅
💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步