【C进阶】自定义类型(2)位段 - 枚举 - 联合

目录

1、位段

      1.1、什么是位段

      1.2、位段的内存分配

      1.3、位段的跨平台问题

      1.4、位段的应用

2、枚举

      2.1、枚举类型的定义

      2.2、枚举的优点

      2.3、枚举的使用

3、联合

      3.1、联合类型的定义

      3.2、联合的特点

      3.3、联合大小的计算


1、位段

1.1、什么是位段

  • 位段的声明和结构是类似的,有两个不同:
  1. 位段的成员必须是 int、unsigned int 或signed int 。
  2. 位段的成员名后边有一个冒号和一个数字。
  • 比如:
#include<stdio.h>
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	printf("%d\n", sizeof(struct A)); //8
	return 0;
}
  • A就是一个位段类型。那位段A的大小是多少?
  • 解析:

上述代码中,_a : 2 表示 _a 只需要2个比特位,_b : 5 表示 _b 只需要5个比特位,_c : 10表示_c只需要10个比特位,_d : 30表示 _d 只需要30个比特位。将这些比特位全部+起来总共47个bit位,何来8字节呢?需要了解下面的位段内存分配:

1.2、位段的内存分配

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

_a是int类型的,先开辟4个byte,_a占2个bit,还剩30bit,_b占5个bit,还剩25bit,_c占10个bit,还剩15bit,15个bit的大小不够_d的30bit大小,且_d还是int类型的,所以再次开辟4个byte,装下了_d的大小,所以位段A的大小8字节。

  • 再比如:
#include<stdio.h>
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	printf("%d\n", sizeof(struct S));//3
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

此时a是char类型的,先开辟1个字节,a占了3个bit,还剩5个,b占了4个bit,还剩1个bit,不够c的5个bit,再开辟1个字节,赋给 c 5个bit位,还剩3个,又不够给d了,再开辟1个字节足矣。综上此时开辟3个字节的大小。此情况是存在空间浪费的。那1个bit位被浪费了,但是没办法。

  • 位段在一定程度上可以节省空间,但也会适当浪费空间(很小)。
  • 画图解释具体空间开辟情况:

为了证明图中的结论,在VS编译器打开监视看看内存:

1.3、位段的跨平台问题

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

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

1.4、位段的应用

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

2、枚举

  • 枚举顾名思义就是一一列举。把可能的取值一一列举。

  • 比如我们现实生活中:
  1. 一周的星期一到星期日是有限的7天,可以一一列举。
  2. 性别有:男、女、保密,也可以一一列举。
  3. 月份有12个月,也可以一一列举
  • 这里就可以使用枚举了。
  • 注意:枚举类型的大小始终4字节

2.1、枚举类型的定义

#include<stdio.h>
enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
int main()
{
	printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
	return 0;
}

以上定义的 enum Day,是枚举类型。{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。 从运行结果看,不难发现枚举是有初始值的,默认第一个枚举常量为0,向下依次+1。当然在定义的时候也可以赋初值。

  • 例如:
#include<stdio.h>
enum Day//星期
{
	Mon = 1,
	Tues,
	Wed,
	Thur = 3,
	Fri = 8,
	Sat,
	Sun = 0
};
int main()
{
	printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
	return 0;
}

2.2、枚举的优点

#define MALE 4
#define FEMALE 5
#define SECRET 6

typedef enum Sex
{
	MALE=4,
	FEMALE,
	SECRET
}Sex;
  • 我们可以使用 #define 定义常量,为什么非要使用枚举?
  • 枚举的优点:
  1.  增加代码的可读性和可维护性
  2.  和#define定义的标识符比较枚举有类型检查,更加严谨。
  3.  防止了命名污染(封装)
  4.  便于调试
  5.  使用方便,一次可以定义多个常量

2.3、枚举的使用

enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5; //ok??  err错误,左右类型不一致

3、联合

3.1、联合类型的定义

  • 联合也是一种特殊的自定义类型

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

#include<stdio.h>
//联合类型的声明
union Un
{
	char c; //1
	int i;  //4
};
int main()
{
	//联合变量的定义
	union Un u;
	//计算联合变量的大小
	printf("%d\n", sizeof(u)); //4
	return 0;
}
  • 联合体的访问和结构体类似都可以采用 . 操作符或 -> 指向操作符。
union un x;
x.a = 10;
union un* p = &x;
p->a;

3.2、联合的特点

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

#include<stdio.h>
union Un
{
	char c; //1
	int i;  //4
};
int main()
{
	union Un u;
	//计算联合变量的大小
	printf("%d\n", sizeof(u)); 
	printf("%p\n", &u);
	printf("%p\n", &u.c);
	printf("%p\n", &u.i);
	return 0;
}

  • 利用联合体的空间分布可以巧妙判断出大小端:
#include<stdio.h>
union un
{
	int a;
	char b;
};
int main()
{
	union un x;
	x.a = 1;
	if (x.b == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}
  •  解析:

这里要把一组二进制序列 0x 00 00 00 01保存在a对应的空间里,此时四个字节每个都有地址,而地址具有高低之分,而我们的数据按字节对应的1bit位进行划分的时候,数据就有高低权值位之别,所以存储方案有两种,一种高权值位放在高地址处,低权值低地址处,如我们上图的左边存法,01放在低地址处,第二种方案相反,如上图右边将01放在高地址处,因为b永远在a的低地址处,b占一个字节,如上图x.b=1红色记号笔划分出,如果存储方案是第一种,那么b=1,如果是第二种,那么b=0,而第一种存储方案正式小端的存储法则,第二种正是大端的存储法则。

由编译器运行得知,是小端:

3.3、联合大小的计算

  1. 联合的大小至少是最大成员的大小。
  2. 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
  • 例如:
#include<stdio.h>
union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1)); //8
	printf("%d\n", sizeof(union Un2)); //16
	return 0;
}
  • 解析Un1的大小为8:

因为char类型自身大小为1字节,而编译器默认对齐数是8字节,对齐数取较小值为1字节,int类型自身大小4字节,对齐数取较小值为4字节。此时最大对齐数4字节,而char c[5]代表至少是5字节大小,又因为要是最大对齐数4的整数倍,所以增加到8字节。

  • 解释Un2的大小16字节:

short c[7]代表该联合体大小至少14字节,short类型本身大小2字节,编译器默认对齐数8字节,此取较小值为2字节,int类型本身4字节,编译器8字节,此对齐数取较小值为4字节,最大对齐数为4字节,而14不是4的整数倍,所以增加到16字节。

猜你喜欢

转载自blog.csdn.net/bit_zyx/article/details/122685180