【C语言进阶篇】 位段 枚举 联合 —— 自定义类型的详细解析!

举报
鸽芷咕 发表于 2023/09/17 20:38:26 2023/09/17
【摘要】 位段 枚举 联合体 这些有些在C语言的学习中可能听过,但是都不是很了解!今天就个大家详细讲解一下,让你彻底搞懂这些是什么

@TOC

📋 前言

  🌈hello! 各位宝子们大家好啊,前面一章给大家带来了结构体的深层次 讲解,那么接下来就来到下一章的学习了,铁铁们准备好了嘛?
  ⛳️本期给大家带来的是 位段的内存分配 枚举 联合(共用体)的详细讲解让我们一起学起来把!
  📚本期文章收录在《C语言进阶篇》,大家有兴趣可以看看呐
  :tent: 欢迎铁汁们 :heavy_check_mark: 点赞 👍 收藏 ⭐留言 📝!

💬 位段

  ⛳️一般情况下在书里,结构体的章节过来了之后就是位段(位域)叫法不同而已,说明我们的位段是靠结构体来实现的!

💬 什么是位段

位段的声明和结构是类似的,有两个不同:

  • 1.位段的成员必须是 int、unsigned int 或signed int 。
  • 2.位段的成员名后边有一个冒号和一个数字。***(冒号后面的数字不得超过前面类型的大小.)***

📚 代码演示:

struct A
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

⛳️ 这里A就是一个位段类型。那位段A的大小是多少?

  • ==会是我们心里想的16个字节嘛?==

📑 代码结果:

在这里插入图片描述

诶这时你会发现这个结构体的内存对齐计算结构体大小完全不一样啊!为什么大小是这个呢?

  • 就算是按4个整形计算也不可能是8个字节啊
  • 下面我们就来介绍一下位段的内存是如何分配的!

🔥 ==注:结构体如何计算大小文章链接==《结构体的内存对齐》

  ⛳️ 位段位段,说明他是位的截段 那么是什么位呢?答案是 比特位 !,每个数字是占多少个 比特位

  • ==大家可以验证一下,这些数字加起来刚好32位==
  • ==而32个 比特位 4 个字节刚好能装下==
  • 大家看一下下面这个猜一下是不是我们计算的4呢!
struct B
{
	int _a : 2;
	int _b : 5;
	int _c : 15;
	int _d : 10;
};

📑 代码结果:
在这里插入图片描述

哦!的确是这样的。下面我们就来具体看下位段到底是怎么样开辟空间的

💭 位段的内存分配

📜说明:

  • 🌱 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  • 🌱 位段的空间上是按照需要以 4 个字节( int )或者1个字节( char )的方式来 开辟 的。
  • 🌱 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

  ⛳️ 这些是什么意思呢?就是我们位段的成员必须是整形家族的,如果我们位段时int 类型的那么就会先开辟4个字节,不够在开辟4个字节这样一直开辟下去, char类型的也是同理!

📚 举个例子:

#include <stdio.h>
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

  ⛳️那么究竟是不是我们这样描述存储的实践一下看看就明白了,假设我们在vs2019 这个平台上是从低向高访问的

  • ==并且按照我们分配内存的慷慨性一旦放不下就重新开辟空间!==
  • 而我们的位段 是char 类型的说明先开辟一个字节 ,不够在开辟
  • 那么我们大致画一下内存分布并运行查看一下到底是不是这样的?
    🔥 ==注:vs2019的情况下成员存放是先从右往左用,但是内存开辟是从地地址向高地址开辟==在这里插入图片描述

这里我们想象的大致内存分布是这样的,用了3个字节,程序运行看下是不是这样的?

📑 代码结果:
在这里插入图片描述

哦!看来在vs这个环境里就是按我们想像的这样开辟空间的我们在验证一下

  • 我们位段的每个成员都存放数据,然后到内存里面看是不是这样存储的
  • 但是每个成员只占他所对应的 比特位 存放
  • ==所以一旦存放的内容超过所能存储的内容就会发生截断==

位段成员 a 在内存中占3个字节但是 10 这个数字要存放 4 个字节,所以我们就会截断

  • 10 的二进制位是1010
  • 截断3位就是 010所以我们 a 存放的就是010
    在这里插入图片描述

我们把存放进去的二进制位转成16进制来看一下!

  • 因为是 16进制 所以 4 个比特位 为一个 16进制
    在这里插入图片描述

⛳️这里就可以看到按我们这存放的话,转换出来的16进制是 0x 62 03 04 00

  • 我们在vs2019里面调试看一下存放的和我们一步一样!

📑图片展示:
在这里插入图片描述

这时就可以看出在vs2019就是按我们想的那样存储的!

  • ==即,位段中的成员在内存中(我们在vs是从低地址到高地址存储的)==
  • ==一旦分配的字节不过够存放下一个成员,会开辟新的字节来存放新成员==

==注:但是在C语言中从左向右分配,还是从右向左分配标准尚未定义!==

==注:当第一个位段的剩余的内容无法存储第二个位段时,要开辟新的空间,那之前剩余的空间是否被利用取决于平台,也没有规定.==

💻 位段的跨平台问题

⛳️ 跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
    舍弃剩余的位还是利用,这是不确定的。
  1. int 位段被当成有符号数还是无符号数是不确定的。

    • 这就是说关于我们整形的最后一位,当成有符号数还是无符号数C语言没有规定!
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    器会出问题。

    • int 类型在我们的早期是 2个字节的,因为早期的机器位最大为16
    • 所以我们按现在int的大小4 个字节 写成27位的话再小机器上就会有问题
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

    • 刚刚我们在给大家模拟了,位段的存储但是这只是vs 里面的情况
    • 在linux gcc 编译器又是另一种情况了!这也是位段的跨平台问题
    • 🔥 ==注:在vs里面内存使用是从右往左,但是我们开辟空间是从地地址向高地址开辟!==在这里插入图片描述
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
    舍弃剩余的位还是利用,这是不确定的。

    • 这也是和第三种一样的情况充满了不确定性,C语言没有明确规定
      在这里插入图片描述

💬 什么是枚举

枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举

这里就可以使用枚举了。

💭 枚举类型的定义

⛳️ 那么我们怎么定义枚举类型呢?其实和结构体差不多

  • 枚举类型的关键字是 enum
  • 那么我们来假设定义一下三元色 (红,绿色,黑色)

📚 代码演示:

enum Color//颜色
{
	 RED,
	 GREEN,
	 BLUE
};
  1. { } 中的内容是枚举类型的可能取值,也叫 枚举常量 。
  2. 这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。

📚 代码演示:

#include <stdio.h>
enum Color//颜色
{
	RED = 1,
	GREEN = 9,
	BLUE = 4
};
int main()
{
	printf("%d", RED);
	printf("%d", GREEN);
	printf("%d", BLUE );
	return 0;
}

📑 代码结果:
在这里插入图片描述

💻 枚举的优点

我们可以使用 #define 定义常量,为什么非要使用枚举?

📜枚举的优点:

  1. 增加代码的可读性和可维护性
  2. #define 定义的标识符比较枚举有类型检查,更加严谨。
  3. 便于调试
  4. 使用方便,一次可以定义多个常量

⛳️ #define 定义的标识符比较枚举有类型检查,更加严谨。

  • 枚举是个类型,所以我们在使用的时候就要注意了
  • 一旦定义的是个枚举类型,就只能给他赋枚举类型的变量成员
    🔥 ==注:在C语言里面类型检查不是很严谨所以我们用C++程序测试的。==
    在这里插入图片描述

💭 枚举的使用

📚 代码演示:

#include <stdio.h>
enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};

int main()
{
	enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值
	return 0;
}

💬 联合(共用体)

⛳️联合也是一种特殊的自定义类型
⛳️这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

  • ==他的关键字是 union==

💭 联合类型的定义

⛳️ 那么我们如何定义联合体呢?下面就来看一下假如来定义一个un 联合体

📚 代码演示:

#include <stdio.h>
union un
{
	char c;
	int i;
};

int main()
{
	printf("%d", sizeof(un));
	return 0;
}

📑 代码结果:
在这里插入图片描述

啊!这里就有许多铁汁们要问了,一个 int 一个 char 怎么也得5个字节,而这里才用了4个字节!

  • 而这就是联合体的特点了,这些成员公用同一块空间(所以联合也叫共用体)

💻 联合的特点

  ⛳️联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。

  • 我们把每个成员的地址打印出来就可以看到他们的确是用一块地址的!
  • ==所以公用体同一时间只能用一个==

📑图片展示:
在这里插入图片描述

举个例子:

#include <stdio.h>
union un
{
	char c;
	int i;
};

int main()
{
	union un un = { 0 };
	// 下面输出的结果是一样的吗?
	printf("%p\n", &(un.i));
	printf("%p\n", &(un.c));
	//下面输出的结果是什么?
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);
	
	return 0;
}

有人就会问了,既然我们是公用一块内存的那么先把 i 里面放满内容。然后 使用 c 变量 i 会改变嘛?

  • 我们来大致画一下内存分布图来运行检测一下
    在这里插入图片描述

📑 代码结果:
在这里插入图片描述

哦~!看来的确是像我们前面画的那样存储的!

💭 联合大小的计算

  • 🌱 联合的大小至少是最大成员的大小。
  • 🌱 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

🔰 是不是非常简单,下面我们就留俩个例题给大家练习练习!

union Un1
{
 char c[5];
 int i;
};
union Un2
{
 short c[7];
 int i;
};
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));

📝全篇总结

✅ 归纳:
好了以上就是关于 位段 枚举 联合 就全部讲解完毕啦!
  位段的内存分配
  位段的跨平台问题
  枚举类型的定义
  枚举的优点
  联合大小的计算
:cloud: 好了把这些知识点全部掌握就可以彻底搞懂, 位段 枚举 联合 啦!快去试试吧
看到这里了还不给博主扣个:
⛳️ 点赞:sunny:收藏 :star: 关注

💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。
在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。