位段
位段的定义
位段与结构体
-
位段其实使用结构体来实现的。
-
位段与结构体的语法格式类似,但位段与结构体有3个不同:
- 位段的成员必须是整型: int、unsigned int、signed int 或 char 类型。
- 位段的成员名后有一个冒号和一个数字。
- 位段的成员类型必须相同,有一个int就全是Int,有一个char就全是cha
位段的功能
- 在一定程度上节省不必要的空间。
位段的语法格式
- 位段的(位)表示的是比特位。
- 冒号后面的数字表示该成员变量占多少个比特位。
- 位段的第一个成员类型直接决定整个位段的成员类型。
struct A
{
int a : 2; //a 占 2 个比特位
int b : 5; //b 占 5 个比特位
int c : 10; //c 占 10个比特位
int d : 30; //d 占 30个比特位
};
- 为何所有的位段成员总共只占用了 47 个比特位,算起来应该 6 个字节就够了,然而位段类型 A 的大小却是 8 个字节?
位段的内存分配
- 有些值的取值范围返常有效,实际上用不到 1 个字节那么多,就可以使用位段限制这些值的取值范围来节省内存。
- 比如:int flag,flag 是用来判断真假的,取值范围只有 0 和 1 两种数据,只需要 1 个比特的空间就够了,给他 1 个字节太浪费了。
位段的内存开辟规则
- 位段的成员必须是整型: int、unsigned int、signed int 或 char 类型。
- 位段的空间上是按照需要以 4 个字节( int )或者 1 个字节( char )的方式来开辟的。
- 每次固定开辟 4 或 1 字节往里面放成员,空间不够时再开辟下一块 4 或 1 字节的空间。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
分析位段的大小
- 分析下列位段所占空间为何是这样。
struct A
{
int a : 2; //2 Bit
int b : 5; //5 Bit
int c : 10; //10 Bit
int d : 30; //30 Bit
};
struct B
{
char a : 3; //3 Bit
char b : 4; //4 Bit
char c : 5; //5 Bit
char d : 4; //4 Bit
};
1. 分析 struct A 的大小为何是 8 字节
2. 分析 struct B 的大小为何是 3 字节
位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。
- 16 位机器最大16,32 位机器最大 32,写成27,在16位机器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结
- 如果编写的代码 “ 肯定 ” 不会拿到其他平台,代码还是少用位段为好。
枚举
枚举的定义
- 列举出所有可能的取值称之为枚举。例如:
- 一周 7 天,可以一一列举出来。
- 一年 12 月,可以一一列举出来。
- 枚举类型属于常量,无法被修改。
枚举的语法格式
enum 枚举类型名 {枚举值名称1,枚举值名称2, 枚举值名称3, ... 枚举值名称n};
声明枚举类型例子
- 将一周的可能取值都列举出来。
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
枚举的默认值与赋值
1. 枚举的默认值
- 枚举常量为 int 类型的常量。当不对枚举常量赋初始值的时候,枚举常量的值默认是从 0 开始依次往下递增。
enum Color
{
RED, //0:第一个枚举常值默认为 0
GREEN, //1:从前一个常量值往后递增
BLUE //2
};
2 给枚举常量赋初值
- 枚举常量也可以被赋予初始值。当前面的枚举常量被赋初值,而后面的没有时,后面的枚举常来值依然为前面的递增 1。
enum Color
{
RED = 520,
GREEN,
BLUE = 100
};
枚举的使用
根据枚举类型定义枚举变量
- 使用枚举类型定义的枚举变量只能对枚举变量赋枚举常量。
enum Color
{
RED = 520,
GREEN,
BLUE = 100
};
int main()
{
//c 只能被赋予 RED、GREEN、BLUE 这三个值
enum Color c = RED;//√
enum Color q = 30; //×
}
使用枚举常量
- 当需要定义很多同类型的常量的时候,不需要写一大堆的 #define。
- 假设要定义一堆蔬菜的价格,用 #define 定义就显得很乱。要写一堆的 #define 并且无法一眼看出它们属于同类。
#define potato 5
#define tomato 3
#define cucumber 2
#define onion 1
......
- 但是如果放到枚举里面,首先就可以知道这些属于同一属性的数据,其次根据枚举类型名也能直接分别这些属性是什么。
enum price //可以判断出列举出的都是价格
{
potato = 5, //只能赋予整型
tomato = 3,
cucumber = 2,
onion = 1
};
枚举的优势
相较于 #define 而言 enum 的优势
- 增加代码的可读性和可维护性。
- 和 #define 定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)。
- 便于调试。
- 使用方便,一次可以定义多个常量。
联合(共用体)
- 联合也是一种特殊的自定义数据类型。
- 这种类型定义的变量也包含一系列的成员。
- 特征是联合体成员共用一块空间(地址),所以联合也叫共用体。
联合体的声明
- 声明联合(共用)体的语法格式与结构体一致。
union 联合体名称
{
联合体成员1;
联合体成员2;
联合体成员3;
};
- 只需要将 struct 换成 union,结构体就变成了共用体。
- 虽然结构相似,但是共用体的所有成员拥有同一个内存地址。
联合体的特点
- 联合体的成员共用同一块内存空间。
- 联合体变量的大小,至少是最大成员的大小。
- 例如:联合体内有 int 成员以及 char 成员,联合体的大小就至少得是 4 字节。
- 联合体内的成员一次只能使用一个,不能同时使用。
- 在前面已经对某联合体成员赋值时,继续对其他成员赋值,后面的值会覆盖前面的值。
union Un
{
char a;
int b;
};
1. 联合体共用同一个地址
2. 共用体的大小至少是最大成员大小
3. 对联和体赋值会导致互相覆盖
- 联合体就像一个 “ 人格分裂者 ” 一样,同一时间这具身体只会出现一个人格,后出现的人格会将前面的人格覆盖。
union Un
{
int a;
char b;
};
int main()
{
union Un u;
u.a = 5;
u.b = 'a';//将 5 覆盖
printf("u.a = %c\n", u.a);
printf("u.b = %d\n", u.b);
return 0;
}
联合体判断字节序
解题思路
- 在联合体内放置一个 char 成员,以及一个 int 成员。
- 因为 char 占据 int 的第一个字节。如果是小端存储,char 存着的就应该是 01,反之为 00。
int check_sys()
{
union Un
{
char a;
int b;
}u;
u.b = 1;
return u.a;
}
int main()
{
if (check_sys()) //返回 1 为小端
{
printf("小端\n");
}
else //返回 0 为大端
{
printf("大端\n");
}
return 0;
}
计算联合体大小
- 联合体在计算大小的时候也存在对齐。
联合体内存对齐规则
- 联合体的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
分析 Un 的大小