新号别搞:结构体+联合体+枚举
1、 联合体 (Union)
- 使用
union关键字。 - 编译器只为最大的成员分配足够的内存空间。
- 所有成员共用同一块内存空间,它们的起始地址是相同的。
- 特性:给其中一个成员赋值,其他成员的值也会跟着变化。
1.1 大小计算规则
- 联合的大小至少是最大成员的大小。
- 对齐规则:当最大成员大小不是最大对齐数的整数倍时,要对齐到最大对齐数的整数倍。
1.2 大小端判断
int check() { int i = 1; // 在内存中存储为 00 00 00 01 if (*(char*)&i == 1) // 取低地址的一个字节 return 1; // 小端 else return 0; // 大端 }小端模式:低位字节存放在低地址。所以取出第一个字节就是 01
大端模式:高位字节存放在低地址。所以取出第一个字节就是 00
1.3相同成员的结构体和联合体对比
2、枚举
2.1 声明
enum Color { RED, GREEN, BLUE };枚举顾名思义就是⼀⼀列举
2.2 默认值
从0开始,依次递增 1(即 RED=0, GREEN=1, BLUE=2)。当然也可以手动赋值。
2.3 枚举的优点
- 增加代码的可读性和可维护性
- 和#define定义的标识符⽐较枚举有类型检查,更加严谨。
- 便于调试,预处理阶段会删除 #define 定义的符号
- 使⽤⽅便,⼀次可以定义多个常量
- 枚举常量遵循作⽤域规则,枚举声明在函数内,只能在函数内使⽤
2.4 举例
enum Color//颜⾊ { RED, GREEN, BLUE };{}中的内容是枚举类型的可能取值,也叫 枚举常量 。取值都是有值的,默认从0开始,依次递增。
3、结构体 (Struct)
3.1 结构体类型的声明与初始化
基本语法:struct tag { member-list; } variable-list;
3.1.1 结构体变量的创建和初始化
struct Stu { char name[20];//名字 int age;//年龄 }; int main() { //按照结构体成员的顺序初始化 struct Stu s = { "张三", 20, "男", "20230818001" }; printf("name: %s\n", s.name); printf("age : %d\n", s.age); }3.1.2 特殊声明:
- 匿名结构体:省略标签名,通常用于只定义一次的情况。
- 自引用:结构体内部包含指向同类型结构体的指针.
3.1.3 定义结构体不要使⽤匿名结构体
typedef struct Node { int data; struct Node* next; }Node;3.2 结构体内存对齐
01. 结构体的第1个成员对⻬到和结构体变量起始位置偏移量为0的地址处
02. 从第2个成员变量开始,都要对齐到某个对齐数的整数倍的地址处
03. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,
所有对齐数中最大的)的整数倍。
04. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍 处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
3.2.1 对⻬数
编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
VS 中默认的值为 8 Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3.2.2 例子
空间换时间 故而尽量让占用空间小的成员尽量集中在⼀起
3.2.3修改默认对⻬数
#pragma pack(1)//设置默认对⻬数为1
#pragma pack() //取消设置的对⻬数,还原为默认
3.3 结构体传参
参数要压栈,会有时间和空间上的系统开销。结构体过大,开销较大,性能会降低,故而传地址。
struct S { int data[1000]; int num; }; struct S s = { {1,2,3,4}, 1000 }; void print2(struct S* ps) { printf("%d\n", ps->num); } int main() { print2(&s); //传地址 return 0; }3.4 结构体实现位段
3.4.1 位段声明+结构
- 01. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他整型家族类型。
- 02. 位段的成员名后边有⼀个冒号和⼀个数字
struct A { int _a : 2; int _b : 5; };3.4.2 位段的内存分配
位段的成员可以是 int unsigned int signed int 或者是 char 等类型
位段的空间上是按照需要以4个字节( int )或1个字节( char )的⽅式开辟的。
位段涉及很多不确定因素,位段不跨平台,注重可移植的程序应避免使⽤位段。
3.4.3 位段的跨平台问题
- int 位段被当成有符号数还是⽆符号数是不确定的。
- 位段中最⼤位的数⽬不能确定
- 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
- 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利⽤,这是不确定的。
3.4.4 位段使用注意事项
- 位段的⼏个成员共有同⼀个字节,有成员的起始位置并不是某个字节的起始位置,没有地址。
- 内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
- 不能对位段的成员使⽤&操作符,不能scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员
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; }完
