FEATURED · 精选文章

结构体_联合体_枚举

发布时间 / 2026/8/15 23:37:04
来源 / 创域科博编辑部
栏目 / 资讯中心
结构体_联合体_枚举 目录一、结构体1.1 结构体类型的声明1.1.1 结构体声明和初始化1.1.2 typedef重命名结构体结构体自引用规则1.2 结构体内存对齐1.2.1 对齐规则1.2.2 修改默认对齐数1.3 结构体传参1.4 结构体实现位段1.4.1 位段的内存分配1.4.2 位段的跨平台问题1.4.3 位段的应用1.4.4 位段使用的注意事项二、联合体2.1 联合体类型的声明2.2 联合体的内存特点2.2.1 相同成员的结构体和联合体内存对比2.3 联合体大小的计算2.3.1 练习三、枚举3.1 枚举的声明3.2 枚举类型的优点3.3 枚举类型的使用一、结构体1.1 结构体类型的声明含义结构体是C语言的自定义聚合类型用于将多个不同类型的数据打包为一个整体变为一个新的类型。1.1.1 结构体声明和初始化struct 结构体标签名 { 类型名 成员名1; 类型名 成员名2; // ... }; // 末尾分号是语法强制要求不可省略例如描述一个学生// 先声明类型 struct Stu { char name[20]; int age; char sex[5]; }; // 后定义变量 struct Student s1; struct Student s2; // 或者声明变量的同时定义变量 struct Student { char name[20]; int age; char sex[5]; }s1,s2; // 直接在末尾定义变量结构体的特殊声明匿名结构体struct { int a; char b; } x, y; // 仅此处可定义变量结构体成员初始化定义变量时赋值1. 初始化定义变量时赋值//完全初始化 struct Stu s1 {zhangsan, 18}; //部分初始化剩下置0 struct Stu s2 {lisi}; //age 02. 同类型结构体变量直接整体赋值✅struct Stu s1 {zhangsan,18}; struct Stu s3; s3 s1; //整体拷贝把s1全部成员复制给s3注这是拷贝不是共用内存s3 修改不会影响 s1。结构体支持 直接赋值数组不能直接用 整体赋值3. 逐个成员赋值struct Stu s4; s4.age 20; // s4.name wangwu; ❌错误数组名是地址不能直接赋值 strcpy(s4.name, wangwu); //字符串要用strcpy拷贝4.结构体指针访问成员struct Stu s {zhang,18}; struct Stu* ps s; ps-age 22; //指针用 - strcpy(ps-name,lisi);常见错误汇总1.❌ s.name abc;name 是 char 数组char数组在结构体中不能直接赋值必须用strcpy。2.❌ 不同结构体类型不能互相赋值。总结同类型结构体变量可以直接整体赋值完成全部成员拷贝内部字符串数组成员不能直接赋值要用 strcpy。观察下面有趣代码struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20],*p; // 在上面代码的基础上下面的代码合法吗 p x;警告1.编译器会把上面的两个声明当成完全不同的两个类型所以是非法的。2.匿名结构体类型如果没有进行重命名的话基本只能使用一次。1.1.2 typedef重命名结构体用 typedef 给结构体重命名后续定义变量无需再写 struct 关键字。写法1无标签版不支持自引用typedef struct { char a; int b; } t_t; // t_t 是新类型名 t_t s1; // 直接用别名定义变量写法2带标签版支持自引用链表 / 树必备typedef struct Node { int data; struct Node* next; // 自引用必须使用指针 } t_t; // 新类型名 t_t node1;结构体自引用规则1.不能包含自身类型的普通成员会导致大小无线循环编译错误。2.只能包含指向自身类型的指针。自引用时在匿名结构体自引用时提前用新类型创建成员变量可行吗typedef struct { int data; Node* next; }Node;typedef 重命名的别名要等整个结构体声明完成后才会生效在结构体内部别名还未被定义无法使用。正确自引用结构体必须带有标签不使用匿名结构体。typedef struct Node { int data; struct Node* next; }Node;1.2 结构体内存对齐1.2.1 对齐规则1.结构体的第1个成员对齐到结构体变量起始位置偏移量为0的地址处。2.从第2个成员变量开始都要对齐到某个对齐数的整数倍的地址处。对齐数 编译器默认的一个对齐数 与 该成员变量大小 二者中的较小值。VS的默认的值为 8linux 中 gcc 没有默认的一个对齐数对齐数就是成员自身的大小3.结构体总大小为最大对齐数的整数倍。结构体中每个成员变量都有一个对齐数4.如果嵌套了结构体嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。练习struct S1 { char c1; int i; cahr c2; }; struct S2 { char c1; cahr c2; int i; }; struct S3 { double d; char c; int i; }; struct S4 { char c1; struct S3 s3; double d; }; printf(%zu\n,sizeof(struct S1)); printf(%zu\n,sizeof(struct S2)); printf(%zu\n,sizeof(struct S3)); printf(%zu\n,sizeof(struct S4));为什么结构体存在内存对齐简单说结构体的内存对齐是拿空间来换取时间的做法。在设计结构体时为了既满足对齐又节省空间应该让占用空间小的成员尽量集中在一起。1.2.2 修改默认对齐数利用 #pragma 预处理指令#includestdio.h #pragma pack(1)//设置默认对齐数为1 struct S { char c1; int i; char c2; }; #pragma pack()//取消设置的对齐数还原为默认 int main() { printf(%d\n,sizeof(struct S)); return 0; }1.3 结构体传参#includestdio.h struct S { int arr[5] {1,2,3,4,5}; int num 100; }s; //传值调用 void print1(struct S t) { for(int i 0;i 5;i) { printf(%d ,t.arr[i]); } printf(\n); printf(%d\n,t.num); } //传址调用 void print2(const struct S* ps) { for(int i 0;i 5;i) { printf(%d ,ps-arr[i]); } printf(\n); printf(%d\n,ps-num); } int main() { print1(s); print2(s); return 0; }上述代码中print1 和 print2 哪个更好print2 更好原因如下1.函数传参时参数是需要压栈会有时间和空间上的开销。2.如果传递一个结构题体对象的时候结构体过大参数压栈的系统开销比较大所以会导致性能下降。总结结构体传参的时候要传结构体的地址。1.4 结构体实现位段概念位段也叫位域是 C 语言基于结构体实现的内存精细化控制语法允许手动指定结构体成员占用的二进制位数(bit)核心目的是节省内存常用于硬件寄存器、协议报文等对内存极度敏感的底层场景。位段和结构体的不同1.位段的成员必须是 int、unsigned int 或 signed int 在C99中位段成员的类型也可以选择其他整型家族类型比如char。2.位段的成员名后面有一个冒号和数字。基础语法在结构体成员声明后加位数指定该成员占用的比特数。struct 位段名 { 类型 成员名1 : 位数1; 类型 成员名2 : 位数2; // ... };注1.成员类型通常为 unsigned int、int、unsigned char类型2.位数必须是非负整数不能超过对应类型的总比特数。那么位段所占内存的大小是多少#includestdio.h int main() { struct A { int a:2; int b:5; int c:10; int d:30; }; printf(%zu\n,sizeof(struct A)); return 0; }1.4.1 位段的内存分配1.位段的成员可以是 int、undigned int、char 等类型。2.位段的空间上是按照需要以4个字节int或者1个字节char的方式来开辟。3.位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。利用下面代码理解位段内存空间是如何开辟的//⼀个例⼦ struct S { char a:3; char b:4; char c:5; char d:4; }; struct S s {0}; s.a 10; s.b 12; s.c 3; s.d 4; //空间是如何开辟的如图1.4.2 位段的跨平台问题1.int 位段被当成有符号数还是无符号数是不确定的。2.位段中最大的数目是不确定的。16位机器最大1632位机器最大32写成27在16位机器会出问题3.位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。4.当一个结构包含两个位段第二个位段成员比较大无法容纳第一个位段剩余的位时是舍弃剩余的位还是利用这是不确定的。总结跟结构体相比位段可以达到相同的效果并且可以很好的节省空间但是有跨平台的问题。1.4.3 位段的应用下图是网络协议中IP数据的格式我们可以看到其中很多的属性只需要几个bit位就能描述这里使用位段能够实现想要的效果也节省了空间这样网络传输的数据报大小也会较小一些对于网络的通畅是有帮助的。1.4.4 位段使用的注意事项1.位段成员不可取地址内存最小寻址单位是字节单个比特没有独立地址因此不能对成员使用 取地址也不能直接用 scanf 读入到位段成员。2.大小不是比特数简单相加位段以存储单元位为单位分配空间剩余空间不足时会废弃并开启新单元最终整体大小为存储单元大小的整数倍不等于所有位段位数之和。3.零长度位段特殊作用unsigned int 0是专属语法作用是强制结束当前存储单元后续位段从下一个全新存储单元开始并非仅空出 0 比特。struct A { int _a : 2; int _b : 5; int _c : 10; int _d : 30; }; int main() { struct A sa {0}; scanf(%d, sa._b);//这是错误的 //正确的⽰范 int b 0; scanf(%d, b); sa._b b; return 0; }二、联合体2.1 联合体类型的声明概念联合体也叫共用体关键字 union像结构体一样也是由一个或者多个成员构成这些成员可以是不同类型。基础语法union 联合体标签名 { 类型 成员名1; 类型 成员名2; // ... }; // 末尾分号为语法强制要求不可省略仅声明类型时不占用内存定义变量时才分配实际内存空间。四种常见的声明方式1. 先声明类型后定义变量union Data { char c; int i; double d; }; union Data d1, d2;2. 声明类型的同时定义变量union Data { char c; int i; double d; } d1, d2;3. 匿名联合体union { char c; int i; } d1;4. typedef 重命名typedef union { char c; int i; double d; } UData; UData d1; // 定义变量无需再写 union 关键字1. 定义时初始化❌不能直接初始化其他成员只能对第一个成员初始化union Un u1 {0x01}; //等价 u1.c 0x01; union Un u {.i100}; //C99指定初始化可以考试很少见结构体与联合体在赋值方面对比✅相同点1.同类型变量都支持直接 变量2 变量1 整体赋值完整拷贝全部内存字节。2.普通变量用.指针用 - 访问成员。3.如果内部包含char[]数组数组不能直接赋值要用 strcpy。❌核心不同结构体各个成员独立内存给其中一个成员赋值不会影响别的成员。2.2 联合体的内存特点内存分配规则1. 联合体总大小 占用空间最大成员的大小因为联合体至少得有能力保存最大的成员并且联合体总体大小必须是所有成员中最大对齐数 的整数倍。2. 起始地址一致所有成员都从联合体的首地址开始存储共享同一块内存起点。观察内存#includestdio.h union UN { char c; int i; }; int main() { union UN un {0}; un.i 0x11223344; un.c 0x55; printf(%x\n,un.i); return 0; }上述代码发现将i的第四个字节的内容修改为了55un的内存局部图如下观察地址#includestdio.h union UN { char a; int b; }; int main() { union UN un { 0 }; printf(un %p\n,un); printf((un.a) %p\n,(un.a)); printf((un.b) %p\n,(un.b)); return 0; }观察发现上述代码三者的地址完全相同。2.2.1 相同成员的结构体和联合体内存对比//结构体 struct S { char c; int i; }; struct S s {0}; //联合体 union Un { char c; int i; }; union Un un {0};内存对比图2.3 联合体大小的计算1.联合体的大小至少是最大成员的大小。2.当最大成员大小不是最大对齐数的整数倍的时候就要对齐到最大对齐数的整数倍。#includestdio.h union Un1 { char c[5]; int i; }; union Un2 { short c[7]; int i; }; int main() { printf(%zu\n,sizeof(union Un1)); printf(%zu\n,sizeof(union Un2)); return 0; }2.3.1 练习1. 某商场搞促销活动要上线一个礼品兑换券礼品兑换单中有三种商品图书、杯子、衬衫。每种商品信息如下图书书名、作者、页数杯子设计衬衫设计、可选颜色、可选尺寸利用联合体归纳上述信息并且使得所使用的空间尽可能的小struct gift_list { int stock_number;//库存量 double price; //定价 int item_type;//商品类型 union { struct { char title[20];//书名 char author[20];//作者 int num_pages;//⻚数 }book; struct { char design[30];//设计 }mug; struct { char design[30];//设计 int colors;//颜⾊ int sizes;//尺⼨ }shirt; }item; };2. 利用联合体判断当前机器是大端还是小端。#includestdio.h union Un { char a; int b; }; int check_sys() { union Un un; un.b 1; return un.a; } int main() { if(check_sys() 1) { printf(小端\n); }else { printf(大端\n); } return 0; }优化版int check_sys() { union { char a; int b; }un; un.b 1; return un.a; }三、枚举含义把可能的取值进行一一列举。3.1 枚举的声明enum 枚举名 { 枚举常量1, 枚举常量2, 枚举常量3 };3.2 枚举类型的优点我们可以使用 #define 定义常量为什么非要使用枚举枚举的优点1.增加代码的可读性和可维护性。2.和 #define 定义的标识符比较枚举有类型检查更加严谨。3.便于调试预处理阶段会删除 #define 定义符号。4.使用方便一次可以定义多个变量。5.枚举常量是遵循作用域规则的枚举声明在函数内只能在函数内使用。3.3 枚举类型的使用enum Color { RED 1, GREED 4, BLUE 8 }; enum Color clr GREED;注在C语言中是可以拿整数给变量赋值的但是在C中不行C的类型检查比较严格。最后感谢阅读欢迎大家交流与指正。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻