你好,我是史丰源
欢迎你的来访,希望我的博客能给你带来一些帮助。
我的Gitee: 代码仓库☀️
我的联系方式:
QQ:1756786195
邮箱:Marksky126@outlook.com
自定义类型详解
- :star2:一、结构体
- 1.1 结构体的基础知识
- 1.11我们为什么需要结构体?
- 1.2 结构体的声明
- 1.3 结构体的特殊声明(匿名结构体)
- 1.4 结构的自引用
- 1.5 结构体变量的定义和初始化
- 1.6 结构体的内存对齐:fire:
- 1.61 如何计算结构体的内存大小?
- 1.62 为什么存在结构体内存对齐?
- 1.7 修改默认对齐数
- 1.8结构体传参
- :star2:二、位段
- 2.1 什么是位段?
- 2.2 位段的内存分配
- 2.3 位段的跨平台问题(不安全因素)
- 三、枚举类型(枚举:一一列举。)
- 3.1 枚举类型的定义
- 3.2 枚举的优点(与#define相比)
- 3.3 枚举的使用
- 四、联合(共用体)
- 4.1 联合类型的定义(union)
- 4.2 联合的特点
- 4.3 联合大小的计算
一、结构体
1.1 结构体的基础知识
1.11我们为什么需要结构体?
C语言中的类型是单一的,如果我们需要去形容一个复杂对象,就需要结构体.
之所以叫结构体,可以看作它是一个立体的存储单元,将各种各样的成员(特点)集合为一体(结构体).
而里面的成员 可以是各种各样类型的变量
1.2 结构体的声明
struct Student{int age;char name[20];int id;};
以上就是一个学生结构体的声明。
1.3 结构体的特殊声明(匿名结构体)
struct{int a;char b;}s;
匿名结构体顾名思义就是省略了结构体标签。
并且只能用一次。
一般在定义结构体类型的同时定义结构体变量。
注意:匿名结构体的成员如果一样,在编译器看来也是不同的类型的结构体。
✨举例子:
struct{int a;char b;}s;struct{int a;char b;}x,*p;//p为结构体指针p=&s;//行为非法,编译器还是会把它们当作两个不同的结构体,//所以不能地址赋值地址。
1.4 结构的自引用
✨举例子:
struct Node{int data;struct Node* next;};//一定不要丢掉*
1.5 结构体变量的定义和初始化
- 普通初始化
✨举例子:
struct student{char name[20];char id[12];}s={"zhangsan","1234"};
- 嵌套初始化
struct price{struct student s;float c;}s={{"zhangsan","1234",57.7}};
1.6 结构体的内存对齐
1.61 如何计算结构体的内存大小?
内存对齐规则:
第一个成员在与结构体变量偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数: 编译器的默认一个对齐数与该结构体成员大小的较小值。
(后面会讲到怎样修改默认对齐数。)
- 结构体总大小为所有结构体成员中对齐数最大值的整数倍。
4.若有嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体大小就是所有最大对齐数(含嵌套结构体)的整数倍。
注意:对齐数不是成员大小,笔者第一次听这个概念时,分不清两者
✨举例子:
上面这个例子就是常见的结构体计算大小问题。
我们再用一个例子来解释 规则4
✨举例子:
规则4 的易错点:嵌套的结构体对齐到自己的最大对齐数的整数倍
一定要记住是自己的,在示例中就是s1自己的最大对齐数— 4,所以要找4的整数倍。
1.62 为什么存在结构体内存对齐?
因为结构体是一个不同类型数据的集合,当我们在读取这个结构体数据时
会出现以下的情况:
但是如果我们进行内存对齐:
所以进行内存对齐,数据的读取次数会减少
从而来实现效率最大化。
这个过程就是利用空间换时间的做法⚡️
所以我们在设计一个结构体时,尽量让占用空间小的变量集中在一起。
1.7 修改默认对齐数
#pragma pack(4);struct S{char c;double d;}//答案为16#pragma pack()//取消修改默认对齐数,还原为默认。
1.8结构体传参
结构体传参时,建议不要传值,因为传值时会造成大量的时间、空间浪费。
建议传址,只有一个地址的大小,效率更高。
二、位段
2.1 什么是位段?
位段与结构体类似,但是有两点不同。
- 位段的成员必须是int,unsigned int,signed int,char(类属于整型家族)。
- 位段成员后方跟一个冒号与一个数字(比特位)。
强调:位段不进行内存对齐。
✨举例子:
struct A{int _a:2;//2个比特位int _b:5;//5个比特位int _c:10;//10个比特位int _d:30;//30个比特位//共47个字节}
位段A的大小是 8 字节。
2.2 位段的内存分配
- 位段的空间上是按照4个字节或1个字节开辟的。
- 位段的内存分配不确定。
✨举个分配不确定的例子:
struct A{char _b:3;char _c:6;} struct A a = {0};//将结构体A中元素置为0a._b = 10;
2.3 位段的跨平台问题(不安全因素)
int 位段是被当作signed int 还是unsigned int是不确定的。
位段中最大位的数目不确定并且可移植性不好,若是32位机器开32个比特位,在16位机器就会出问题。
位段中分配内存是从左到右,还是从右到左不确定。
当一个位段的位不够用时,是开辟新的位还是利用剩余的位,这也是不确定的。
综上:位段的效率很高,但是有利必有弊,位段也有跨平台的问题会出现。
大家需根据特定需求使用位段。
三、枚举类型(枚举:一一列举。)
3.1 枚举类型的定义
✨举例子:
enum Sex{MALE,//0FEMALE,//1SECRET//2};
{ }中的内容都是枚举类型的可能取值,也叫做枚举常量。
这些可能取值都是有值的,默认从0开始,依次递增。
但我们也可以给它初始化。
✨举例子:
enum I{M=9,K=7};
注意 :初始化只能在枚举类型内,而不能在其他地方初始化。
3.2 枚举的优点(与#define相比)
- 增加代码的可读性与可维护性
✨举例子:
enum Color{RED = 5,GREEN = 4 BLUE = 3};int main(){enum Color c=RED;}
- 和#define定义的标识符相比 枚举类型有类型检查(检查的是类型,而不是值),更为严谨
PS:关于这个问题大家可以去看这篇文章:C语言的枚举类型检查问题.
限制了范围(封装)
在一定范围内才能使用。便于调试(不会被替换)
在预编译时,#define 所定义的常量会直接变为数字(调试的代码与源代码不一样),而枚举类型则不会(调试代码与源代码一致)。使用方便,一次可以定义很多个常量
3.3 枚举的使用
✨举例子:
enum Color{RED = 5,GREEN = 4,BLUE = 5};enum Color clr = BLUE
四、联合(共用体)
具体场景具体使用
4.1 联合类型的定义(union)
联合类型定义的变量包含很多成员,特点就是这些成员共用一块空间(因此也称为共用体)
4.2 联合的特点
- 共用一块空间。
- 在改变一个变量时,其他变量同时被改。
- 成员在同一时间只能使用一个,不能同时被使用。
- 联合类型变量的大小至少是最大成员的大小(4.3中演示)
4.3 联合大小的计算
1.联合大小至少是最大成员的大小
2.当最大成员大小不是最大对齐数的整数倍时,要对齐到最大对齐数的整数倍。
✨举例子:
union Un1{char c[5];//1*5==5 不是最大的int i;//4};//最终大小为8,4为最大对齐数union Un2{short c[7];//2*7==14 不是最大的int i;//4};//最终大小为16,对齐数为4
在找最大对齐数时,要看联合体成员类型,确认对齐数,比如说:Un1的char为1,int 为 4,而不是看后面的数组个数来计算。
找最大对齐数的倍数 ——最靠近并且能容纳所有变量大小的数。
感谢你看到这里,希望我的博客对你有帮助。
我是一名大学生,在学习编程的道路上,我们一起努力!