一、位段
1.1位段的声明
struct 对象名{
位段的成员列表;(类型 变量名:数字;)
};//别忘记分号
位段与结构体的声明,除了成员列表不同外,其余格式皆相同
首先,位段的成员类型必须是int(unsigned int或者signed interesting)(小于等于int的都可以),
其次,位段的成员名后紧跟着冒号和数字,数字代表着该变量所占比特位的大小,举例:
struct S{
char a:3;
char b:4;
char c:5;
char d:4;
};
位段与结构体的区别无非是在成员列表的要求上稍有不同,限制了每个成员的存储空间,例如上述代码的a只有3个比特位的空间,b只有4个比特位的空间,c只有5个比特位的空间,a,b,c各自使用自己的空间来存储自身相应的数据,当自身数据太大超出自身分配空间大小时,只能存储数据的一部分,而当开辟剩余空间不足以完全存储一个成员变量时,则会重新开辟空间
1.2位段的内存分配
当位段成员是int类型时,每次开辟4个字节大小的空间,(当位段成员是char类型时,每次开辟1个字节大小的空间),然后依次存储位段成员,当剩余空间不够完整存储下一个成员时,再开辟一个4个字节大小的空间来存储,但是位段成员在利用这4个字节大小的空间时,是从左到右,还是从右到左,并没有做出规定,我们假设是从右到左利用
例如
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;
printf("%d ",sizeof(s));
}
因为是位段,而且成员是char,因此每次开辟1个字节的空间,8个比特位,初始化s全部为0,因此如下
初始化a=10,a的二进制为1010,但只给a分配了3个比特位的空间,不能完全存储的下,因此只能从低字节到高字节存储a的3个比特位,即010,如下
b=12,二进制为1100,b的空间是4个比特位,可以存储,如下
c=3,二进制为11,第一次开辟的8个比特位空间只剩下一个,不足以完全存储c,因此重新开辟一个字节的空间来存储c,且c一共占据5个比特位的空间
d=4,且占据4个比特位,剩余3个比特位不够,因此重新开辟空间存储d,二进制为0100
那么,位段变量s在内存中存储的二进制位应为0110 0010 0000 0011 0000 0100,转化为16进制为 62 03 04 ,我们来看一下调试结果
我们再来看看成员是int类型的位段所占空间大小
struct A
{
int a:2;
int b:5;
int c:10;
int d:30;
};
成员是int类型,首先开辟四个字节的空间,分别存放a,b,c后,剩余的空间不足以存储d,因此再次开辟4个字节的空间来存储d,一共8个字节,来看看调试结果
1.3位段小结
位段的使用能够让程序员更加合理的利用空间,但是它本身也存在着一定的问题
- int 位段被当成有符号数还是无符号数是不确定的。也就是说int类型数据二进制位的首位是否被当作符号位不确定,有兴趣的可以亲自测试一下
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。)这个好理解,成员类型所占字节数就是最大位的数目,但是不同机器下相同类型所占字节不同(int)
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。但是我们可以亲自实验
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。个人理解为位段中包含一个位段时,开辟空间的剩余位不足以完整存储分配给成员位段的位时,不确定会这么进行存储,当然,如果成员不是位段时,此时肯定会再开辟空间(舍弃)
二、枚举
2.1枚举的声明
enum 对象名{
枚举成员列表;//准确来说是枚举对象的可能取值
//直接书写变量名,无需书写类型
};
枚举的声明和结构体的声明相比,除了关键字从enum变为struct以及成员无需类型名外,其余书写格式均相同,但枚举和结构体不同的是,
枚举的含义表示可以一一列举,因此如果不给枚举的成员赋值,那么从第一个枚举成员开始,会默认为0,第二个为1,第三个为2,以此类推,如果在某个枚举成员处赋值,那么它之前的成员不受影响,之后的成员会在它的基础上递增,因此
我们经常把枚举的成员称之为枚举常量,它们是枚举对象所有可能的取值
并且枚举常量前虽然没有书写类型,但是它们本身是有类型的,他们的类型就是enum 对象名。
2.2枚举的优点
- 增加代码的可读性和可维护性。看到枚举就能明白它是什么
- 和#define定义的标识符比较枚举有类型检查,更加严谨。当我们用define定义某个常量时,其实该常量只是一个标识符,在接下来的代码中,我们只要看到这个常量的名字,就会自动转化为define定义时该常量对应的内容。所有说define定义的数据其实没有类型,而枚举常量是有类型的
- 防止了命名污染(封装)。命名污染的意思就是名字重复出现
- 便于调试。源代码->预编译->编译->链接->可执行程序,预编译的作用就是去除注释以及转化define定义的标识符这些冗余的东西。枚举的存在可以代替define,使得代码更加便于调试
- 使用方便,一次可以定义多个常量。使用一次枚举,可以同时定义多个枚举常量,比define定义更加方便
2.3枚举的使用
只能用枚举常量或者强制转化为枚举类型来给枚举变量赋值
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
int main()
{
enum Color clr = RED;
//enum Color clr = (enum Color)1;
}
三、联合(联合体)(共用体)
3.1联合体的声明
union 对项名{
成员列表;
};
3.2联合体的特点
所有成员共用一块空间,即每个成员的起始地址相同,然后按照本身大小向后存储
3.3联合体的大小
联合体的大小最小是联合的大小至少是最大成员的大小。因为联合体的特点就是共用空间,当能存储最大的那个时,其他的肯定没问题
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。这里的最大对齐数就是自身对齐数和默认对齐数(8)中的较小值,
union Un1
{
char c[5];//c前面的类型是1,因此自身对齐数为1,和8比较后的较小值为1
int i; //比较后的对齐数为4
};//最大对齐数为4,但不能存储数组c(5),因此翻倍,为8
union Un2
{
short c[7];//比较后对齐数为2
int i;//比较后对齐数为4
};//最大对齐数为4,但不能存储数组c(14),所以为16
调试结果如上
标签:存储,int,成员,C语言,位段,枚举,空间 From: https://blog.51cto.com/u_15466618/6138632