网站建设项目描述范文,wordpress首页调用产品,数据网站建设成本,深圳营销型网站建设-龙华信科目录 结构体
1.不完全声明
2.结构体的自引用
3.定义与初始化
4.结构体内存对齐与结构体类型的大小
结构体嵌套问题
位段
1.什么是位段#xff1f;
2.位段的内存分配
枚举
1.枚举类型的定义
2.枚举的优点
联合#xff08;共同体#xff09;
1.联合体类型的声明以…目录 结构体
1.不完全声明
2.结构体的自引用
3.定义与初始化
4.结构体内存对齐与结构体类型的大小
结构体嵌套问题
位段
1.什么是位段
2.位段的内存分配
枚举
1.枚举类型的定义
2.枚举的优点
联合共同体
1.联合体类型的声明以及变量定义
2.联合体的特点
利用联合体判断当前机器是大端还是小端
3.联合体大小的计算 结构体
结构体的基础知识以前的博客已经介绍过了这里是进阶版
1.不完全声明
在创建结构体类型的时候可以省略标签如图 这种结构体被称为匿名结构体如果这样创建那我们就只能在创建类型的同时创建变量无法在其他地方创建变量比如这里的x就是在创建此匿名结构体类型的同时创建的变量。
如果我们再创建一次匿名结构体类型 他与上面的匿名结构体成员变量均相同但是我在这里创建了一个结构体指针p那么px这种写法正确吗答案是不正确编译器会把上面的两个声明当做不同的类型。
2.结构体的自引用
有这样一段代码 对于这样一段代码如果用sizeof来计算这种类型的大小直接就计算不出来因为在结构体类型里面又包含了一个同样类型的变量那这个变量势必又会包含一个int类型的data和又一个结构体类型的变量无限套娃都无法计算这种类型的大小显然这样的自引用是错误的。
正确的自引用方式是使用指针 来看下面一段代码 我们先把一个匿名结构体类型进行重命名为Node然后使用他在主函数里面创建变量可以吗
答案是不可以这就像我们遇到的经典问题先有鸡还是先有蛋再创建类型的时候我都还没有重命名为Node居然就先使用了显然是错误的。
正确写法 3.定义与初始化
可以先创建类型再初始化也可以在创建类型的同时初始化
如图是在创建类型的同时初始化 初始化的时候可以直接像sn1一样按顺序写这样编译器认为是按照结构体成员出现的顺序初始化的也可以使用点操作符这样就可以按照自己的顺序来初始化。
4.结构体内存对齐与结构体类型的大小
运行这样一段代码 我们想要计算这两个结构体类型的大小而这个结构体类型里面成员变量有一个int类型和两个char类型大小加起来应该是六个字节但是我们打印出来发现结果居然是12和8不仅不是6甚至都两次结果都不一样大这是为什么呢
通过offsetof计算出struct s1各个成员变量离起始地址的偏移量分别是0,4,8,offsetof是一个宏具体用法这里就不展开讲了于是我们可以画出struct s1的成员变量在内存中的分布 那也就是占了九个字节为什么struct s1的大小会是12呢
这就涉及到了内存对齐
首先得掌握结构体的对齐规则
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字对齐数的整数倍的地址处。
对齐数 编译器默认的一个对齐数与该成员大小的较小值。VS中默认的值为8gcc环境没有对齐数如果成员变量是一个数组则他的对齐数是数组中元素的字节大小与默认对齐数的较小值比如char ch[5]{0};他的对齐数大小就是1
3. 结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍。
4. 如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍。
知道了这些规则之后再来看上面的struct s1在内存中为什么这样存储struct s1的第一个成员变量是char类型的因此对齐数是1因此第一个成员变量就放在了偏移量为零的单元第二个成员变量i是int类型的对齐数是4应该放偏移量是在4的整数倍的单元处因此从4开始放四个字节浪费了三个字节单元第三个成员变量也是char类型的对齐数是1因此接着放一个字节如图所示 一共占用了9个字节但是根据第三条规则结构体类型的大小必须是所有成员变量中最大对齐数的整数倍struct s1的成员变量最大对齐数是49不是4的倍数此时离着4的倍数最近的字节数是12因此struct s1的大小是12个字节。
同样的再来看struct s2 他的第一个成员变量是int类型的从偏移量为零的地址处开始存放放了四个字节第二个成员变量是char类型的对齐数是1直接接着放就行因为任何数都是1的整数倍。第三个成员变量也是char类型的对齐数是1接着放一个字节发现一共用了6个字节最大对齐数是4,6不是4的倍数离着最近的一个4的倍数是8因此struct s2的大小是8
结构体嵌套问题 struct s4类型的第一个成员变量是char类型的对齐数是1直接在偏移量为0的地址处开始存放第二个成员变量是struct s3类型的他的成员变量最大对齐数是8因此他要对齐到偏移量为8的整数倍的位置离着最近的就是从偏移量为8的位置开始存放存放多少个字节那就要看struct s3类型占多少个字节。
我们假设又有一块空间存放着struct s3
struct s3的第一个成员变量是double类型对齐数是8从偏移量为0的地址处开始存放存放了8个字节存到了偏移量为7的位置第二个成员变量是char类型的对齐数是1接着存一个字节存到了偏移量为8的位置第三个成员变量是int类型的对齐数是4但是接下来要存的单元偏移量是9不是4的整数倍因此要从偏移量为12的位置开始存放四个字节到了偏移量为15的位置。又因为struct s3的成员变量最大对齐数是8因此struct s3所占的字节大小就是离15最近的8的整数倍也就是16.
再回到struct s4的存储从偏移量为8的单元开始放16个字节到了偏移量为23的位置struct s4的第三个成员变量是double类型的对齐数是8而接下来要存放的位置偏移量是24恰好是8的整数倍因此接着存放8个字节单元到了偏移量为31的单元处。共计占用了32个字节。所有成员变量的最大对齐数是8,而32恰好是8的整数倍因此struct s4的大小是32个字节
为什么存在内存对齐
1. 平台原因(移植原因)不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。
2. 性能原因数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于为了访问未对齐的内存处理器需要作两次内存访问而对齐的内存访问仅需要一次访问。
总体来说结构体的内存对齐是拿空间来换取时间的做法。
注可以通过#pragma pack(x)来设置默认对齐数为x如果想还原默认的对齐数就再调用一次#pragma pack()括号内什么也不写就行了
5.结构体传参
这在初阶的时候已经介绍过可以传一个结构体变量这样访问的时候就用点操作符也可以传一个结构体指针这样访问的时候就用箭头操作符推荐使用传结构体指针的形式因为传结构体变量会导致形参在内存中开辟一块与原来结构体一样大的内存造成内存的浪费。再者如果是传的结构体变量实际上他是实参的一份临时拷贝对他进行的任何操作都不会改变实参因而我们就无法通过调用函数来操作实参了。
位段
1.什么是位段
位段的声明和结构是类似的有两个不同
1.位段的成员必须是 int、unsigned int 或signed int或char 。
2.位段的成员名后边有一个冒号和一个数字。冒号后面的数字代表这个成员变量占的比特位。注意是比特位不是字节。
但是位段与结构体是两个不同的概念比如 那么struct A就是一个位段类型
他表示_a占2个比特位_b占5个比特位以此类推。通过sizeof计算A的大小发现是8个字节。
为什么要有位段呢假如我只需要_a表示四种情况那么两个比特位就足够了但是int类型的_a我们却为他开辟了32个bit位实际有30个bit浪费了因此位段是一种节省空间的方法。
2.位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char 属于整形家族类型
2. 位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟的。
3. 位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。
为什么说位段具有不确定因素呢还是以前面的例子为代表 再创建_a的时候开辟了4个字节也就是32个比特位然后用了两个剩下了30个比特位那这里就有不确定因素 比如这2个比特位是在刚才开辟的空间的左边还是右边
接着创建了_b变量占5个字节接着在剩下的30个bit里面放5个接着创建_c又放了10个这时候还剩下15个此时创建_d要占去30个bit剩下的位置已经不够放了只能再申请一块32bit的内存那这里又有不确定因素了新申请的空间能直接放下_d那么原来剩下的15个bit还放不放是先放15个到上一块空间中再放15个bit到新申请的空间中还是直接放30个bit到新申请的空间中这些都是不确定因素C语言并没有规定。 有这样一个位段在我使用sizeof计算这个位段大小的时候发现是3个字节那么S在内存中就有可能是这样存的 我画的是从右往左存的当然也可能是从左往右存的但是目前能确定的是这里如果原来开辟的内存不够用了再申请一块新空间的时候原来剩下不够的那些内存没有使用
再来测试一下是从左往右还是从右往左 对s进行如图的初始化假如是从右往左放的话内存中的存储应该是这样 换算成16进制应该是620304通过的调试发现确实如此。
因此在当前环境下位段就是从右往左不够的空间就不用的方式分配内存。
枚举
1.枚举类型的定义
枚举顾名思义就是一一列举。把可能的取值一一列举。比如我们现实生活中一周的星期一到星期日是有限的7天可以一一列举。 enum Day 就是枚举类型。大括号里面是可能的取值中间用逗号隔开这与结构体类型的声明是不同的结构体类型大括号里面是成员变量用分号隔开。在使用枚举类型创建变量的时候就只能赋值成大括号里面的某个内容比如enum Day xMon;
注只有能一一列举的值才使用枚举类型像身高体重这种可能的取值太多了是不可能使用枚举类型的。
大括号里面列举的内容都是有值的默认是从零开始当然也可以在定义的时候进行赋值如图所示 2.枚举的优点
我们可以使用 #define 定义常量为什么非要使用枚举
枚举的优点1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查更加严谨。
3. 防止了命名污染封装
4. 便于调试
5. 使用方便一次可以定义多个常量
下面是一个枚举使用的例子 实际上这个GREEN就是2但是我们不能直接把2赋给clr增加了代码的可读性。
联合共同体
联合也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员特征是这些成员公用同一块空间所以联合也叫共用体。
1.联合体类型的声明以及变量定义 2.联合体的特点 通过打印地址我们发现c和i的地址居然是一样的这就说明c和i共用了同一块内存单元。这样虽然节省了空间但是会有其他的问题比如我在修改c的同时必然会把i也修改了因此对于联合类型来讲只能用其中的某一个成员变量。
利用联合体判断当前机器是大端还是小端 我们把i初始化为1如果是小端那么这四个字节存的就是01 00 00 0016进制如果是大端这四个字节就是00 00 00 01也就是说我们只需要判断第一个字节的内容即可由于i和c又共用一块内存因此在初始化i的时候c也会被初始化c的内容就是第一个字节单元的内容小端机器高位在高地址第一个字节单元应该是01大端则是00由此我们可以判断出当前机器是大端存储还是小端存储。
注也可以直接创建一个int类型的变量a初始化为1然后
*(char*)a判断是0还是1
3.联合体大小的计算
联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候就要对齐到最大对齐数的整数倍。 union Un1中第一个成员变量占5个字节对齐数是1第二个成员变量占4个字节对齐数是4。
根据上面的规则联合体大小最小的是最大成员变量所占字节大小也就是5但是5并不是最大对齐数也就是4的整数倍离着最近的4的倍数是8因此这个联合体union Un1的大小是8
union Un2第一个成员变量占14个字节对齐数是2第二个成员变量占4个字节对齐数是4,。
union Un2中最大成员变量所占字节为14最大对齐数是4,而14不是4的整数倍离着最近的倍数是16因此union Un2类型的大小是16个字节。