Bootstrap

【C语言】自定义类型(结构体、枚举、联合的详解)下

写在前面

书接上回:【C语言】自定义类型(结构体、枚举、联合的详解)上
在上中,不才独篇撰写了结构体的具体细节,本篇笔记主要是把剩下的自定义类型全部展示。


二、位段

位段的定义方式和结构体相差无几,但是有几处不同

  1. 位段成员可以根据程序猿的需要来自定义位段成员的大小。
  2. 位段的成员必须是int、unsigned int 或signed int
  3. 位段的成员名后边有一个冒号和一个数字。
  4. 位段的成员名后边的数字,代表着这个成员占用多少个比特位。

2.1、位段的定义

举个栗子:

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
  • 位段的类型成员为了与结构体成员等自定义类型成员有所区分,所以在位段成员前面增加下划线来表示此变量为位段成员。
  • _a成员 后面的数组代表着:占用struct A位段空间中的2个比特位。
  • _b成员 后面的数组代表着:占用struct A位段空间中的5个比特位。
  • _c成员 后面的数组代表着:占用struct A位段空间中的10个比特位。
  • _d成员 后面的数组代表着:占用struct A位段空间中的30个比特位。

2.2、位段占用空间的计算

位段的空间占用情况是C语言标准未定义的,所以注重移植性的程序应该谨慎使用位段

本次测试的环境是VS2013 x86环境
我们先使用sizeof操作符计算上面栗子中struct A的大小
在这里插入图片描述
结果为8,即占用了64个比特位,但是我们在struct A中我们只定义了47个比特位,为什么为造成在内存中申请了64个比特位呢,我们需要知道位段的内存分配。

1.位段的成员可以是int unsigned int signed int或者是char(属于整形家族)类型
2.位段的空间上是按照需要以4个字节int)或者1个字节char)的方式来开辟的。根据位段内部定义的类型来开辟 。.但是类型空间内部的使用顺序是C语言标志未定义的,在前个字节中,剩下未使用的比特位是否使用也是C语言标准未定义的
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

注意:每个平台对位段的处理可能都不一样,就连VS2013和VS2022都是不相同的。
vs2022是不忽略前面空出的比特位的。

根据上面内存分配的定义,目前使用VS2013环境

  • struct A中的成员变量是int类型,所以在内存中首先开辟4个字节,在位段中,我们是先使用地址的字节后使用高地址的字节的。在比特位中,也是先使用低位再使用高位,那_a先占用2个比特位,_b再占用5个比特位。如下图
    在这里插入图片描述
  • 此时,_a与_b都已在内存中找到合适的位置存储,接下到_c占用空间,在第一个字节中,还剩下一个比特位没有被占用,但是为了更高的效率,我们是不使用那个字节的。
  • 浪费一个字节往高地址的空白字节中占用比特位,在第二个字节中,只有8个比特位,不足存放10个比特位,就继续向后寻找,直到有足够的比特位。如下图在这里插入图片描述

-此时,_a、_b与_c 都已在内存中找到合适的位置存储,接下到_d占用空间 但是_d需要30个比特位,卸载只剩下8个可以用比特位,就需要继续开辟四个字节,如下图在这里插入图片描述

  • 在开辟4个字节后,存放_d成员后,得到下图。在这里插入图片描述

在得到了每个成员变量占用对应的位置后,我们就开始赋值操作。在内存中是以二进制进行存储的,所以我们需要把所有的10进制数字先转为二进制数组。如下图在这里插入图片描述

因为_a只能存储2个比特位,所以会发生截断,只能保存最低位的2个比特位。如下图
在这里插入图片描述

随后_b的值存入内存,即12的二进制为:1100。_b可以存放5个字节,即可以全部存入。如下图
在这里插入图片描述

_c的值存入内存,即20的二进制为:0001 0100。_c可以存放20个字节,即可以全部存入。如下图在这里插入图片描述

同理,_d的值存入内存可得下图:在这里插入图片描述

整理上图,我们可以得到struct A内存的具体值:在这里插入图片描述

为了验证我们上面的准确性,我们把得到的值翻译成16进制后,与程序对比来验证准确性。

  • 0110 0010 => 32
  • 0000 0000 => 00
  • 0001 0100 => 14
  • 0000 0000 => 00
  • 0000 1111 => 0F
  • 0110 0011 => 63
  • 1110 0011 => E3
  • 0000 0000 => 00

2.3、位段的跨平台问题

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

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

2.4、位段的应用

位段一般使用在网络传输中,在网络传输层中,需要存储大量的表示(如下图),如果每个表示都使用int类型来编写,每个类型都占用4个字节就会导致大量的无用字节占用通道,导致效率下降,我们使用位段就可以完美解决这个问题。
在这里插入图片描述

三、枚举

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

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

3.1、枚举类型的定义

enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
  • 枚举标识符enumDay是枚举标签,与结构体相似
  • 枚举成员称为:枚举常量。每个枚举常量之间需要使用逗号,分隔,不能使用分号
  • 每个枚举创建大括号后都需要加上分号
  • 枚举类型是: 枚举标识符 + 枚举标签。上面例子中的枚举类型为:enum Day。
  • 每个枚举常量都是默认有值的,默认从0开始,依次递增1,也可以在初始化时候赋初值。
  • 枚举常量是常量,不能在程序运行时被赋值和修改本身。只能在初始化时候赋初值(如下),赋初值只能是整形常量表达式。
enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4,
 yellow
 };

enum Color枚举中:

  • RED枚举常量赋了初值为:1
  • GREEN枚举常量赋了初值为:2
  • BULE枚举常量赋了初值为:4
  • yellow没有赋初值,则递增1,即为:5

3.2枚举的使用

enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4,
	yellow
};
int main() {
	enum Color clr1 = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
	enum Color clr2 = yellow;
	printf("GREEN = %d\n", clr1);
	printf("yellow = %d\n", clr2);
	int n1 = (int)GREEN;
	printf("n1 = %d\n", n1);
	return 0;
}

若修改枚举常量则会报错
在这里插入图片描述

enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4,
	yellow
};
int main() {
	enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
	clr = 5;
	printf("%d ", clr);
	return 0;
}

在这里插入图片描述

3.3、枚举的优点

枚举的优点:

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

四、联合(共用体)

4.1联合类型的定义

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

//联合类型的声明
union Un
{
	char c;
	int i;
};
int main() {
	//联合变量的定义
	union Un un;
	//计算连个变量的大小
	printf("%d\n", sizeof(un));
	return 0;
}
  • 计算出的大小是4个字节,说明在联合类型中我们虽然同时有char类型和int类型,但是这两个类型在空间中只占用最大类型的空间,内存分布图如下图:在这里插入图片描述

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

我们打印地址证明内存图正确(上图)

union Un
{
	int i;
	char c;
};
int main() {

	union Un un;
	// 下面输出的结果是一样的吗?
	printf("%d\n", &(un.i));
	printf("%d\n", &(un.c));
	return 0;
}

程序运行结果为:
在这里插入图片描述
说明在内存中联合的存储就为不才画的内存图。

4.2、联合大小的计算

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
  • 对齐数的计算方法与结构体相同
union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main() {
	//下面输出的结果是什么?
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

结果为:
在这里插入图片描述
内存草图为:
在这里插入图片描述
此时,我们可以利用联合体的特性,来判断当前程序是大端存储还是小段存储。

union Un
{
	int i;
	char c;
};
int main() {
	union Un un1;
	un1.i = 0x1;
	printf("%c", un1.c);
	if (un1.c == 0x01) {
		printf("小端\n");
	}
	else {
		printf("大端\n");

	}
	return 0;
}

结果为:小端
在这里插入图片描述


以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 若有帮助不要吝啬点赞哟~~💖💖

ps:表情包来自网络,侵删🌹

;