写在前面
书接上回:【C语言】自定义类型(结构体、枚举、联合的详解)上
在上中,不才独篇撰写了结构体的具体细节,本篇笔记主要是把剩下的自定义类型全部展示。
文章目录
二、位段
位段的定义方式和结构体相差无几,但是有几处不同
- 位段成员可以根据程序猿的需要来自定义位段成员的大小。
- 位段的成员必须是int、unsigned int 或signed int
- 位段的成员名后边有一个冒号和一个数字。
- 位段的成员名后边的数字,代表着这个成员占用多少个比特位。
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、位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
2.4、位段的应用
位段一般使用在网络传输中,在网络传输层中,需要存储大量的表示(如下图),如果每个表示都使用int类型来编写,每个类型都占用4个字节就会导致大量的无用字节占用通道,导致效率下降,我们使用位段就可以完美解决这个问题。
三、枚举
枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
3.1、枚举类型的定义
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
- 枚举标识符
enum
,Day
是枚举标签,与结构体相似 - 枚举成员称为:枚举常量。每个枚举常量之间需要使用逗号
,
分隔,不能使用分号 - 每个枚举创建大括号后都需要加上分号
- 枚举类型是: 枚举标识符 + 枚举标签。上面例子中的枚举类型为: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、枚举的优点
枚举的优点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
四、联合(共用体)
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:表情包来自网络,侵删🌹