赞
踩
前言:Hello大家好,我是@每天都要敲代码!今天就带大家学习一下新的内容;三大自定义类型:结构体struct,枚举enum,联合union;看着内容很少,其实知识点也很丰富;希望下面我的讲解能让大家有所收获!
目录
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。我们需要掌握:结构体类型的声明、结构的自引用、结构体变量的定义和初始化、结构体内存对齐、结构体传参、结构体实现位段(位段的填充&可移植性)!
回顾:数组是一组相同类型的元素的集合!
对于结构体的声明,我们就拿一个例子来解释;更加的容易理解:
❤️例:
匿名结构体的声明是指在声明结构的时候,可以不完全的声明!还是不明白什么意思?我们不妨拿个例子来理解:
❤️例:
❤️1、一个结构体引用另一个结构体
一个结构体引用另一个结构体是完全没问题的!
❤️2、自己的结构体引用自己
结构体里面,自己引用自己,会造成死循环;相当于死递归,是错误的引用!
❤️3、正确的自引用方法
一个结构体不是包含同类型结构体的变量,而是包含同类型结构体的指针,是完全没问题的!
❤️例:
首先先介绍一下对齐规则:
(1)第一个成员在与结构体变量偏移量为0的地址处。
(2)其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的值为8
(3)结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
(4)如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。tips:一定要先理解上面的四句话,在进行习题的练习!
❤️例题1:
⭐️对于结构体S:
(1) c1放在偏移量为0处,且占一个字节!
(2)i占4个字节,直接从偏移量为1处开始?当然不是!其他成员变量要对齐到某个(对齐数)的整数倍的地址处。对齐数 = VS中默认的值为8与 该成员大小的较小值。所以对齐数应该是4,应该从4的倍数开始,要舍去1、2、3这三个字节!最终从4开始:4、5、6、7;如下图:
(3)最终结构体的大小,也要是这个结构体成员变量的对齐数最大值的整数倍!这里也就是4的倍数;结合(2)最终结果就是8!
⭐️对于结构体S2:
(1)c1放在偏移量为0处,且占一个字节!
(2)i占4个字节,直接从偏移量为1处开始?当然不是!其他成员变量要对齐到某个(对齐数)的整数倍的地址处。对齐数 = VS中默认的值为8与 该成员大小的较小值。所以对齐数应该是4,应该从4的倍数开始,要舍去1、2、3这三个字节!最终从4开始:4、5、6、7;
(3)c2占一个字节,对齐数 = VS中默认的值为8与 该成员大小的较小值。所以对齐数应该是1;刚好放到8的位置;如下图:
(4)(3)最终结构体的大小,也要是这个结构体成员变量的对齐数最大值的整数倍!这里也就是4的倍数;结合(2)(3)最终结果就是:12;而不是9(9不是4的倍数);所以最终还是会浪费3个字节9、10、11!
❤️例题2:
⭐️对于结构体S:
通过画图我们知道,S的大小应该是9,但是9却不是4的倍数;所以最终结果就是12!
⭐️对于结构体S2:
通过画图我们知道,S2的大小应该是16,16刚好是4的倍数;所以最终结果就是16!
⭐️对于结构体S3:
通过画图我们知道,S3的大小应该是8,8刚好是4的倍数;所以最终结果就是8!
⭐️对于结构体S4:
通过画图我们知道,S4的大小应该是16,16刚好是8的倍数;所以最终结果就是16!
⭐️对于结构体S5:
对于结构体S5;里面嵌套着结构体S4;首先是c1在偏移量0处,且占1个字节!然后是结构体S4我们已经计算出来是16字节的大小;这就占17个字节就是0-16,下一个位就是17开始;最后d占8个字节,17不是8的倍数,应从8的倍数24开始,数8个就是到31;所以最终结果就是32!
补充:
(1)在设计结构体的时候,既要满足对齐,也要节省空间,怎么做到?
答:让占用空间小的成员尽量集中到一起!(2)为什么存在内存对齐?
答:1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
#pragma预处理指令,可以改变我们的默认对齐数;修改命令是:#pragma pack(n)
❤️例1:
⭐️按照默认对齐数8来计算:
通过画图我们知道,此时大小应该是16,16刚好是8的倍数;所以最终结果就是16!
⭐️按照修改的对齐数4来计算:
通过画图我们知道,此时大小应该是8,8刚好是8的倍数;所以最终结果就是8!
❤️例2:
这里就不在画图了,感兴趣的小伙伴自己动手画图试试吧!
⭐️按照默认对齐数8来计算:
c1从偏移量0处开始,且占一个字节;
i占4个字节,对齐数取4和8的较小值;应从4的倍数4开始,占四个字节:4、5、6、7;
c2从8开始,且占一个字节;所以最终就是0-8共9个字节,取4的倍数就是12!
⭐️按照修改对齐数2来计算:
c1从偏移量0处开始,且占一个字节;
i占4个字节,对齐数取4和2的较小值;应从2的倍数2开始,占四个字节:2、3、4、5;
c2从6开始,且占一个字节;所以最终就是0-6共7个字节,取2的倍数就是8!
⭐️按照修改对齐数1来计算:
c1从偏移量0处开始,且占一个字节;
i占4个字节,对齐数取4和1的较小值;应从1的倍数1开始,占四个字节:1、2、3、4;
c2从5开始,且占一个字节;所以最终就是0-5共6个字节,取1的倍数就是6!
offsetof库函数是计算结构体中某变量相对于首地址的偏移;引用的头文件是<stddef.h>;对于怎么模拟实现offsetof,我们以后回说!这里暂时只掌握如何使用即可!
size_t offsetof( 结构体, 每个成员变量 )
对于结构体的传参,有两种形式:一种是传值调用;一个是传址调用!我们首选传址调用,为什么呢?
(1)函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
(2)如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
❤️例:
位段的声明和结构是类似的,有两个不同:
(1)位段的成员必须是 int、unsigned int 或signed int等 。
(2)位段的成员名后边有一个冒号和一个数字。
❤️例:
注意:
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
总结:位段的意义,是节省内存空间,根据实际的要求来分配(按需分配)所需要的空间。位段分配空间是按照比特位来分配的,一个字节 = 8个比特位!
❤️例:
解释:
对于整型数据int我们就一次开辟4个字节32个比特位;对于浮点型数据char我们就一次开辟1个字节8个比特位!
(1)整型数据,先开辟4个字节32比特位;对于_a占2个比特位,所以还剩下30个比特位;
(2)对于_b占5个比特位,所以还剩下25个比特位;
(3)对于_c占10个比特位,所以还剩下15个比特位;
(4)对于_d占30个比特位,还剩下的15个比特位完全不够用,所以又需要开辟4个字节32比特位;所以总共使用了8个字节!
注意:但是对于(4)的使用,是先使用了(3)剩余的15个比特位,在使用新开辟的;还是把上面的15个比特位直接舍去,全都使用新开辟的空间?这就是一个问题!所以位段肯定是不跨平台的!
(1)int 位段被当成有符号数还是无符号数是不确定的。
(2)位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。)
(3)位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
(4)当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
❤️例:利用一个例子来验证VS中位段是从左到右分配还是从右到左分配?是舍弃剩余还是利用剩余?
画图分析一下:
总结:
(1)在VS中,位段中的成员在内存中从右向左分配(从低地址到高地址分配)!
(2)在VS中,多余的比特位不够用,是直接舍去,开辟新的空间后直接用新的!
(3)大小端讨论的是字节序,一个字节内部的顺序和大小端是没有关系的!
这实际上是属于计算机网络的内容;目前只做简单了解就好!
就是给你要发送的数据加上一些数据包的信息,利用位段来分配位数,比较节省空间!
枚举顾名思义就是一一列举。
❤️例:
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。{}中的内容是枚举类型的可能取值,也叫枚举常量 。这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
枚举类型就是一种类型不是整型;枚举是类型,可以定义变量,但其成员是常量!
枚举类型的大小就是一个整型变量的大小:4
❤️例:
实际应用:我们在设计计算器时,对于case里面的语句,我们使用数字(1 2 3 4)还要思考对应是要执行什么操作?但是如果定义一个枚举,此时case 1:我们就可以直接写成case ADD: 直接就能出来这个选项是要执行什么操作,增加可读性!
联合也是一种特殊的自定义类型;这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
❤️例:
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
❤️例1:
怎么理解呢?我们通过画图来分析一下:
❤️例2:统一初始化
❤️例3:分开初始化
为什么会这样呢?因为对于u.i = 10 初始化的值会替换u.i = 1000上,原来对应的比特位;最终就会造成u.i结果不在是1000;所以在分开初始化的时候,一次只能使用一个!
比如:我们可以利用联合来判断大小端存储!
❤️例:
解析:
我们给i赋值为1;如果在大端模式,在内存中存储就是00 00 00 01,那么我们取出c,虽然c并没有赋值,但是联合体共用同一块内存,所以取出来c的值也就是0。如果在小端模式,在内存中存储就是01 00 00 00,那么我们取出c,虽然c并没有赋值,但是联合体共用同一块内存,所以取出来c的值也就是1。
测试结果:
联合大小的计算
1.联合的大小至少是最大成员的大小--------不一定就是最大成员的大小
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
❤️例1:
char对齐数是1,数组a[5]占5个字节;int i 对齐数是4,且占4个字节!我们取两者最大值5;并且看5是不是最大对齐数4的倍数;不是还是要在浪费3个字节,补充到8个字节;最终结果就是8!
❤️例2:
short本身占2个字节,对齐数也是2,s[5]总共就占10个字节;int i 对齐数是4,且占4个字节!我们取两者最大值10;并且看10是不是最大对齐数4的倍数;不是,还是要在浪费2个字节,补充到12个字节;最终结果就是12!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。