自定义类型
写在前面
自定义类型小节,主要涉及到结构体,共用体,联合体中的
- 计算内存对齐方式
- 结构体,共用体,枚举的区别
- 位段的概念和已知概念的区别
本节内容比较简单,最重要的就是计算内存对齐。
自定义类型
结构体
结构是一些值的集合,这些值叫做成员变量。结构的每个成员可以是不同类型的变量。
结构的声明:
struct st{
member-list;
}variable-list;
//e.g.
struct student{
char name[20];
int age;
char sex[5];
char id[20];
}NAMEOFASTU;
特殊声明,不完全声明:
可以在定义结构体的时候省略掉结构体的标签。但是如果这样,相同的定义方法定义两个匿名结构体,编译器默认是不同的类型。不同类型的结构体不能进行 x = y等操作。
struct{
int a;
int b;
char c;
}x;
结构体自引用
结构体可以设定一个类型就是该结构体本身的成员变量 (指针)。
struct Node{
int data;
struct Node* next;
};
简写:每次都写struct Node非常麻烦,可以用如下方法:
typedef struct Node{
int data;
struct Node* next;
}Node;
本身定义了struct Node类型,而且还typedef了 struct Node 为 Node类型。之后在使用中就可以写成Node 了。
结构体变量的定义和初始化。
定义变量有两种方法:
-
在声明类型的同时就定义变量,如下面例子X所示
-
单独定义结构体变量Y
//方法1 struct A{ int a; int b; }X; //方法2 struct A Y;
初始化变量:定义变量的同时赋值
struct A a = {10, 20};
同样的可以在声明结构体的时候进行初始化变量:
struct A{
int a;
int b;
}X = {10, 20};
可以嵌套初始化结构体:
struct A{
int a;
int b;
};
struct Node{
int data;
struct A a;
struct Node* next;
}n1 = {10, {1, 2}, NULL};
//结构体嵌套初始化和声明
内存对齐规则
第一个成员在与结构体变量偏移量为0的地址处。(无偏移)
其他成员变量要对齐到对齐数的整数倍的地址处。
对齐数 = 编译器默认的对齐数 与 该成员大小的较小值。
VScode中默认的值为4/8.
结构体总大小为最大对齐数的整数倍(每个成员变量都有自己的对齐数)
如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数)。
为什么存在内存对齐?
平台原因
性能原因:拿空间换取时间
如何设计结构体可以做到满足对齐的同时节省空间?
struct s1{
char c1;
int i;
char c2;
};
struct s2{
char c1;
char c2;
int i;
};
计算一下s1结构体的大小为: 4 + 4 + 4 = 12;
s2 : 4 + 4 = 8,因为c1和c2加起来不够4,后面2 + sizeof(int) > 4,所以两个char先对齐得4,加int的4,总共为8.
所以在声明结构体的时候,尽量让占空间小的放在一起,尽量节省空间。
修改默认对齐数:
#pragma pack();//恢复默认的对齐数 #pragma pack(8);//修改对齐数为8
所以在对齐方式不适合的时候,我们可以自己修改默认对齐数。
结构体传参
结构体传参,最好传递指针(地址),用指针去找到其中的成员变量,远比传递结构体本身方便得多。
位段
位段的声明
与结构体类似,但是不同点在于:
位段的成员必须是int (unsigned int,或signed int), char类型(属于整型的类型)。(int型能不能表示负数视编译器而定,比如VC中int就默认是signed int,能够表示负数)。
位段的成员名后面有一个冒号和数字。
位段的定义格式为: type [var]:digits
位段名称var是可选参数,即可以省略。digits表示该位段所占的二进制位数。
如下:
struct A { int a : 2; // 位段a,占2bit位 int b : 5; // 位段b,占5bit位 int c : 7; // 位段c,占7bit位 };
总共占据14bit位,开辟一个int是4个字节,32bit位。14 < 32,所以开辟一个int大小的空间就可以。
上述代码用sizeof查看,发现占用4个字节大小。
位段的注意事项
- 位段的成员都属于整型家族。
- 位段的空间是按照4个字节(int),或者1个字节(char)的方式来开辟的。
- 位段占的二进制位数不能超过该基本类型所能表示的最大位数,比如在VS中int是占4个字节,那么最多只能是32位;
- 无名位段不能被访问,但是会占据空间;
- 不能对位段进行取地址操作;
- 若位段占的二进制位数为0,则这个位段必须是无名位段,下一个位段从下一个位段存储单元 (这里的位段存储单元经测试在VS环境下 是4个字节)开始存放;
- 若位段出现在表达式中,则会自动进行整型升级,自动转换为int型或者unsigned int。
- 对位段赋值时,最好不要超过位段所能表示的最大范围,否则可能会造成意想不到的结果。
- 位段不能出现数组的形式。
位段在内存中的储存方式
对于位段结构,编译器会自动进行存储空间的优化,主要有这几条原则:
-
如果一个位段存储单元能够存储得下位段结构中的所有成员,那么位段结构中的所有成员只能放在一个位段存储单元中,不能放在两个位段存储单元中;如果一个位段存储单元不能容纳下位段结构中的所有成员,那么从剩余的位段从下一个位段存储单元开始存放。(在VS中位段存储单元的大小是4字节).
-
如果一个位段结构中只有一个占有0位的无名位段,则只占1或0字节的空间(C语言中是占0字节,而C++中占1字节);否则其他任何情况下,一个位段结构所占的空间至少是一个位段存储单元的大小;
位段的跨平台问题
- int位段被当成unsigned 或者是 signed 是不确定的,依平台而定。
- 位段的最大位数目不确定,(16位、32位机器不同)
- 位段中的成员在内存中从左向右、从右向左分配标准未定义。
枚举
枚举类型的定义
enum Day{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
Enum 之后的是枚举类型,{ }中的内容是枚举常量,就是枚举类型的可能取值。这些枚举常量默认从0开始,依次递增。在定义的时候也可以赋初值。如下
enum Color{
RED = 1,
GREEN = 2,
BLUE = 3
};
枚举的优点
- 增加代码的可读性和可维护性
- 相比于#define定义的标识符,枚举有类型检查,更加严谨
- 防止命名污染。
- 便于调试
- 使用方便,一次可以定义多个常量
枚举的使用
enum Color{
RED = 1,
GREEN = 2,
BLUE = 3
};
enum Color clr = GREEN; //只可以拿枚举常量给枚举变量赋值,不要用 enum Color clr = 5;
联合(共用体)
联合类型的定义
联合式特殊的自定义类型,包含一系列的成员,一大特征是这些成员共用同一块空间。所以联合体也叫共用体。
//声明
union Un{
char c;
int i;
};
//定义
union Un un;
特点
成员共用一块内存空间,所以一个联合体变量的大小,至少是最大成员的大小,因为联合体至少有能力保存最大的成员。
在内存中存储的大小
- 联合体大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
自定义类型小节完。