结构体位段+联合和枚举
1.什么是位段:
位段的声明和结构是类似的,但是有不同
1. 位段的成员必须是 int 、 unsigned int 或 signed int ,在C99中位段成员的类型也可以 选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
这就是一个位段的展示,每一个成员后面都有一个冒号和数字,这个数字代表的是这个变量占了几个比特位,位端中的位是二进制位。
上图就是第一个是结构体,第二个是位段,那他们的大小是多少呢?
我们可以看到,位段的大小比结构体小了一半,这是怎么计算的呢?
接下来我们来看看。。。
2.位段的内存分配
我们要明白位段内存的分配才能明白他为什么大小是8个字节,在上面的图片里面,我们看见结构体他的没一个整型成员变量,都是四个字节,32个比特位。所以计算得出的结果就是16个字节,接下来让我们一起来看看这个位段的大小为什么是8个字节,
1. 位段的成员可以是 int unsigned int,signed int 或者是 char 等类型
2. 因为它的成员的原因,位段在空间上是要按照4个字节或者1个字节来进行开辟的
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
我们就通过这个图片来进行讲解,我们先来分析一个这个图片,位段S里面有四个成员变量,他的要求是a成员占据3个二进制位,比特位,成员b占据4个二进制位,成员c占据5个二进制位,成员d占据4个比特位。
我们想要明白他是怎么开辟空间的,那我们就取出一段内存空间来进行讲解:
值得注意的是,我们的位段里面的变量都是char类型的变量,因此,我们来开辟内存空间的时候是以1个字节来进行开辟的,但是如果以后我们遇到int类型或者是unsigned int类型的变量,我们就要用4个字节来进行开辟了。。
我们一齐来看这个图片,我们现在取出了一段空间,但是内存的开辟到地址从右边往左边的方向开辟内存还是从左边往右边的开辟内存呢?,,,。。我们就假设他是从右往左的开辟内存,(要注意的是,我们所说的方向是在一个字节里面从右边向左边的使用内存),我们来看第一个字节,a变量使用了3个比特位,然后到了b变量的分配,b在位段里面规定有4个比特位,因为刚才申请了一个字节,含有8个比特位,已经使用了3个,还留下了5个,足够b的分配,那么就给b变量向左申请4个比特位,b变量申请完了之后,就该轮到c变量来进行分配了,c是5个比特位,虽然之前已经申请了一个字节,但是这个字节现在因为变量a和变量b的原因,现在只剩下,1个比特位,这对于c的分配是远远不够的,所以这时候我们就再开辟一个新的字节来存储c变量,我们就在右边开辟一个新的字节,那么前面的那个字节就被浪费掉了,然后第二个字节消耗5个比特位来存贮c变量,第二个字节这时候就剩下3个比特位,但是d变量的大小的要求是4个比特位,这三个是不够的,那么这时候就在开辟一个新的字节来接收存储d变量,我们就在右边在开辟一个新的字节,那么前一个字节的3个剩下的比特位就也是被浪费掉了。。。。。。这时候我们的最后一个字节存储d变量以后就没有变量需要进行存了,那么他剩下的4个比特位还是被浪费掉了。。
接下来我们继续来看这个,因为他在前面给结构体的成员变量进行赋值,我们来看这个赋值的实现过程:a的值是10,那么他化成二进制位就是1010,有4位,但是我们给他的限制就是只有3个比特位,那么这时候我么就取他的后面的3位,010,把他存到我们给a开辟的内存里面,a变量存储完毕以后就轮到了b变量,b的值我们给他赋了12,12化成二进制数字就是1100,也是4个比特位,但是刚好,我们给他的限制就是4个比特位,所以我们就不需要改变什么直接把他的4个比特位填进去就可以了,然后把b变量分配完了以后,我们就开始对c变量进行存储,c的值是3,他的二进制位就是011,但是我们该他的限制是他要有5个比特位,那我们就必须要把这5个比特位给他填满,那么前面的两个空的就用0来进行填充,那么c的二进制位就是00011。然后我们来对变量来进行赋值,因为d是4,他的比特位是0100,他的要求是4个比特位,刚好,我们就把这个给他填进去。这时候,我们把能填的就都填了,有一个要说的就是,他没有填的部分都是0,,因为在主函数刚开始的时候,我们就给这个位段初始化为0,所以我们没有涉及到的就都是0,。
我们这时候确实是给他把这些变量的内存申请了下来,并且我们也把他的值存给了这些变量,但是我们在进行编写操作的时候,我们还是不知道他是否真的和我们与预想的一样,因为我们在上述代码的实现,我们是加入了一些我们的想想,但是我们还是要验证一下我们的猜想到底是否是真的没错,我们就来进行一下调试,因为在监视内存里面,他是以16进制的形式来进行存储的,所以我们就可以计算一下我们得到的结果,看他和内存里面的值是否一样,由于二进制转换成16进制的数字是4个一组数字,所以我们就可以进行计算,结果就是620304,我们打开监视内存,得到的和我们计算的是一样的。
3.位段的跨平台问题
总结就是:
跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
位段他可以给结构体变量指定比特位的大小,可以很好的节省空间。
4.位段的使用
在这里面就可以使用位段。节省空间。
5. 位段使用的注意事项
在位段里面可能有几个成员公用一个字节,但是我们是不能对位段进行取地址操作的,&,因为我们之前学过的,只有每个字节才会被分配地址,比特位是不会分配地址的,比特位是不会有地址的,
朋友们,你们也可以看这个图片,你在进行取地址的操作的时候,因为地址是放在第一个字节的最开始的,你看这个地址,你在对第一个字节进行取地址操作的时候,你取到的不是b的地址,也不是a的地址,你只是取到了第一个字节的地址。所以,我们对位段使用取地址是行不通的。但是我们还是有办法来修改结构体位段地成员变量。
我们可以先创建一个变量,把我们想要得到的数字赋给结构体位段,这样是可以的。
到这里,结构体的故事我们就讲完了,
我们开始了新的篇章-联合体和枚举
⾃定义类型:联合和枚举
1. 联合体类型的声明
2. 联合体的特点
3. 联合体⼤⼩的计算
4. 枚举类型的声明
5. 枚举类型的优点
6. 枚举类型的使⽤
1 联合体类型的声明
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。 但是编译器只为最⼤的成员分配⾜够的内存空间。联合体的特点是所有成员共⽤同⼀块内存空间。所以联合体也叫:共⽤体。 给联合体其中⼀个成员赋值,其他成员的值也跟着变化。
联合体只会给最大的那个成员变量进行赋值。然后所有的成员变量一起共用这个内存,听起来有点奇怪,但确实是这样的。
联合体的关键字是union,结构体的关键字是struct。
2.联合体的特点
我们来看这张图片,一个是结构体,一个是联合体,他们的成员变量是一样的,我们根据之前学过的结构体对齐就可以判断的出来struct结构体的大小是8个字节,但是我们也看到了unoin联合体的大小是4个字节,这正如我们的概念所简述,联合体他只会给最大的联合体成员内存,然后所有联合体成员共用这块最大的内存
我们来看看这个图片,我们测量出了联合体的内存的大小,就是4个字节。同时我们也打印出了这个联合体的地址,以及它的两个成员变量的地址,我们可以发现,他们的地址竟然都是一样的
我们就来看这个图片,很清晰的就可以发现,他们的首地址都是一样的。
3.联合体大小的计算
1.联合的⼤⼩⾄少是最⼤成员的⼤⼩。
2.当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。
我们可以得知的就是,联合体和结构体是一样的,他们都是有对齐的。
联合体也存在对齐。
上面的两句话怎么理解,我们一起来看这张图片,我们通过上述的两句话,联合体的大小至少是最大成员的大小我们来看这两个类型,尽管char[]是一个数组类型的,但是他的对齐数还是看他的单个元素,单个元素char类型,char类型的对齐数是1,默认对齐数是8,所以他的对齐数是1,int类型的对齐数是4,所以,虽然char数组占据了5个字节,但是他的最大成员变量是4,4比5小,所以这时候我们规定他要到最大对齐数的整数倍,那么这个联合体的就是8个字节。
我们在来一个例子,这个大小是14个字节,但是short类型不是最大的成员,所以我们的联合体的大小要对齐到最大成员变量的整数倍。
但是这里我要说的是联合体的一个缺点就是,在联合体里面,成员变量是不能同时出现的,或者说,他们不能同时“有效存在”。因为他们都是在一个内存里面储存着,他们是公用一段内存空间的,所以,在我们修改一个成员变量的时候,那么其他的成员变量也一定就会改变,我们来看下买你的代码
我们定义了一个联合体,int是4个字节,float是4个字节,c是1个字节,那么这个联合体的大小是4个字节。
因为所有成员共享这 4 字节的内存空间,当你给union Data
中的i
赋值时,这 4 字节的内存空间就用来存储int
类型的值;当你给f
赋值时,这 4 字节的内存空间就会被重新解释为存储float
类型的值。
我们在来做一个题,思考一下,他是不是可以使用结构体。
用了结构体之后,我们需要什么结构体成员变量,我们就调用哪个,但是这有一个缺点,就是他占据的内存空间有点大了,我们能不能使用一下我们今天学习的东西,当然可以
兄弟们,我们看这个代码,结构体里面嵌套了联合体,那么它的好处是什么呢?
对比我们之前的结构体,我们省略了很多的内存空间,使用联合体大大节省了我们的内存空间。
而且因为这三样东西,他是不会同时出现的,所以我们使用完全没有问题。
我们在介绍衣服的时候,可以直接调用,介绍水杯也可以直接调用,非常方便。
还记得那个判断编译器是大端还是小端吗?我们现在定义了一个整型变量a,4个字节,我们把它强转成char*类型的指针,然后解引用进行访问,看看第一个是不是1,如果是1,那他就是小端,如果是0,那么他是大端。
我们现在来看一种新的方法,我们给int类型进行赋值,然后我们返回,也是因为他们是共用内存的,所以修改i之后,c也会被修改。
枚举的声明
枚举的名字是enum
枚举顾名思义就是⼀⼀列举。 把可能的取值⼀⼀列举。
⽐如我们现实⽣活中:
一周有7天,星期一到星期天,这些都是可以一一列举的
性别有男生和女生,这也可以列举。
这些数据的表示,都是可以使用枚举的。
枚举的值使用%d打印整数的方式是可以打印出来的,在默认情况下枚举的取值是从0开始的,逐次的递增1。
枚举是一个常量,它的值是可以被赋予的,我们给他赋值并打印。
值得注意的是:枚举是一个常量,他是不能修改的,他不是变量,无法修改。
那么枚举的有什么优点呢??
定义常量的话,我们也可以使用#define来进行定义常量,为什么非要使用枚举呢?
很大的一个好处就是,它可以一次定义多个常量