一次通关结构体、位断、联合体和枚举这四大痛点
导读:
一到七是结构体的内容,其中重中之重是第七点,结构体大小的计算
第八点是对位断的讲解
第九点是联合体,讲解了两个联合体的应用
第十点是枚举,但不是很重点
一.什么是结构体
1.结构体:其实就是自定义数据类型,结构是值的集合,这些值可以是不同类型的变量
(变量:标量,指针,数组,甚至调用其他结构体)
二.结构体语法格式
2.1先声明类型再定义变量
记得声明在int主函数之前
2.2声明的同时定义
2.3匿名结构体
匿名结构体类型,只能使用一次,后期不能使用这个类型来创建变量,因为没有名字
struct
{
……
}x,y,z
struct
{
……
}x,y,z
2.4结构体的自引用
(数据结构里链表的节点)
struct jiedian struct
{ {
int date; int date;//数据域
struct jiedian next; struct jiedian* next //指针域
}; };
左边不可以的呀,因为左边如果成立,那么struct jiedian占多少字节,有无限个节点,无限大?显然不可能,而且他还包含了一个date,成员加起来比自己都大
改进:右边一半存数据,一半存指针
(如果重命名【详见第六点】为jiedain,不可以把右边的struct jiedain*next,写成jiedain*next,这里面是先定义然后才重命名的,而不是直接把结果拿来使用)
三.结构体的访问
3.1普通结构体的访问
在键盘上找到.这个操作符
想给结构体里的变量赋值,只需在变量名之后点.一下就可以大胆赋值了
他的初始化与数组类似,一般是按成分顺序,如图
不按照顺序初始化需要结构成员访问操作符.(点)
大概为结构体变量.结构体成员名
如果是.cpp后缀的文件就没办法打乱顺序初始化,.c后缀的才行
2.5变量可以随意设置在这些位置
3.2结构体指针访问
使用->这个操作符
如图,这样可以开始对s1变量大胆赋值了
四.结构体里嵌套结构体
类比数组,二维数组是一个特殊的一维数组,只不过他的元素从单个属性变成单个数组
对应的,结构体里面可以有一个结构体来充当元素
所以,你掌握了结构体嵌套结构体,演示如下
五.结构体数组
5.1结构数组定义
相同的元素可以用数组装起来
相同的结构体元素同样可以用结构体装起来,演示如下
5.2结构体传参
如图,可以把结构体当做参数传递到函数里面
但是更推荐传址调用,传值调用理论上还要再创建一块空间来存贮这个结构体里的变量,对时间和空间都有开销
六.结构体重命名
结构体每次定义变量都要像例如 struct Stu s1这样命名
但如图重命名之后,struct Stu==N,省去大量代码
七.结构体内存对齐(计算大小)
7.1引入
1.
如图,只不过是把char 和int 调换了顺序,内容保持不变,但用sizeof算出来的大小是不一样的
这就涉及到大小的计算,实际上,结构体在内存里面是存在对齐的,就像学校建宿舍,有的宿舍是住满的,有的却没有,但最开始建造的时候是不会因为某间宿舍没住满就建小,最后算大小的时候也是计算的所有宿舍能容纳的大小,而不是计算实际占据。
2.偏移量
因为并没有存满,所以对于真实情况的存储,假设一个起始位置,然后算每一个成员相对于偏移量的移动,可以确定到底哪个字节是有占用的
c语言标准库里提供了测量的宏
offsetof--->头文件stddef.h
测出对于s2这个结构体,一共浪费了六个字节,每个成员的偏移量为048
7.2对齐规则
1.结构体的第1个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.从第2个成员变量开始,都要对齐到某个对齐数的整数倍的地址处。从第2个开始逐个考虑偏移量
对齐数------------------->编译器默认的一个对齐数与该成员变量大小的较小值,最小的“宿舍”是每个元素的对齐数的整除倍,比如1,4,8,就为4,1,8,8,就为8
VS中默认的值为8 ,Linux中gcc没有默认对齐数,对齐数就是成员自身的大小
3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小 就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
7.3对齐原因
1.不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取得特定数据
2.数据结构(尤其是栈)应该尽可能的在自然边界上对齐,原因是为了访问未对齐的内存,处理器要做两次内存访问,而对对齐的内存仅需访问一次
3.感悟:尽量把小的数据放一起,会比较节省空间和时间
7.4修改对齐数
#pragma pack(),可以设置默认对齐数,想设置多少就在括号里填写,一般是2的次方数
八.结构体实现位断
1.位断的声明和结构体是类似的,有两个不同,位断的成员必须是int, unsigned int ,signed int(char),还有就是位断的成员名后边有一个冒号和一个数字
如 int_a:2 int _b:5
2.数字表示这个成员要占的比特位的数量,但他依旧存在空间浪费,如图:
理论上A,47个比特位只要6个字节,但是算出来却是8个字节,所有掌握内存分配是很必然的
3.位断的内存分配
位断的空间是按照需求以4个字节或者1个字节来开辟的
因为16个比特位却占据了三个字节,就有以下问题:
一个字节(整型)的内存中,到底是从右向左占据,还是从左向右是不确定的
当剩下的空间不能满足下一个成员的时候,是否浪费不确定
在这里我们可以假设他是从右向左的,然后我们就可以假设不够的时候不占据了
-- - - -- - -。- - -- - - - - -。- - - -- - - - 。
假定是这样的,a是红色,b是绿色,c是蓝色,d是橙色
我直接二话不说,先初始化为0,然后再赋值,
a赋值成10,在二进制位0000,1010,但a只有三个比特位,如果存储的话应该为010
b赋值成12,在二进制位0000,1100,b只有4个比特位,储存1100
此时对ab这个字节来说,他的二进制位为0110,0010就是62
同理换算c和d,应该为,03和04
结果应该就是620304(小彩蛋,这里有涉及大小端这个知识点,你会了没????????)
在内存窗口监视,果然是这样,说明假设很正确,得出如下结论:
一个字节(整型)的内存中,vs选择从右向左占据
当剩下的空间不能满足下一个成员的时候,vs选择浪费
此时可以很好的理解上面的例子为什么是8个字节了
4.位断影响因素很多,最好不要跨平台移植
a。刚才说到的从左向右还是从右向左
b。。是否浪费
c。。。int到底是有符号还是无符号
d。。。。int到底占据几个字节,16位是2个字节,32或64占4个字节
5.位断的应用
网络协议里,什么东西占多大是确定好的,这时候用位断是很OK的,节省了空间,传递也更快,学网络的时候才会深入学习这个
6.注意
位断里面几个成员共有一个字节,这样有的成员的起始地址不是某个字节的起始地址,那么这些位置处是没有地址的,内存中每一个字节分配一个地址,一个字节内部的bit位是没有地址的,所以不能对位断的成员使用&,就不能使用scanf进行输入,只能先输入放在一个变量里面,再赋值给位断
九.联合体
1.共用体(联合体):像结构体一样,联合体也是一个或者多个成员构成,这些成员可以是不同类型的
联合体的关键字是union,联合体的成员共用一块内存,编译器只为最大的成员分配足够空间
所以给联合体其中一个成员赋值,其他成员的值也随之改变
2.联合体的大小至少要能够保存最大的成员,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
联合体的成员之间不能同时使用,一定程度上节省了空间
3.联合体大部分其实和结构体是差不多的,以下讲解一个联合体的应用
场景:
要弄三种商品,每一种商品都有,库存量,价格,商品类型这三种和其他特定信息
图书:书名,作者,页数
杯子:创意设计
衣服:创意设计,颜色,尺寸
这时候设计为结构体,会有很多重复的东西浪费了空间,效率大大下降,联合体出来救场
如图:
4.联合体解决大小端判断
巧借共用的特点来设计代码,通过返回的值来判断大小端,强强强强!!!!
十.枚举
1.
enum,把可能的取值一一列举出来,
2.
{ } 里的是枚举类型的可能取值,也叫枚举常量,这些常量都是有值的,默认从0开始,依次增1,
也就是说,如果以%d的形式打印枚举括号里的第一个变量,他的值就是0
当然也可以在声明的时候给它赋值,比如a=1,这样第一个就变成1了
赋值后依旧往后递增1,且后面的值不可更改
3.有可能结合switch使用,比如曾经的计算器模拟,这时候case直接写add,他和case 0 是一样的,符合了switch的用法,又方便了阅读
枚举的代码可以调试
枚举常量是遵循作用域的,在函数内部的就只能在内部使用
