自定义类型适用于描述复杂对象,比如,人,具有性别、姓名、年龄、身高等属性,不能简单的用某一个类型就能将所有属性都描述出来,所以此时自定义类型更加的方便
自定义类型有结构体(struct)、枚举(enum)、联合体(union)
目录
本章重点:
- 结构体
- 结构体类型的声明
- 结构的自引用
- 结构体变量的定义和初始化
- 结构体内存对齐
- 结构体传参
- 结构体实现位端(位段的填充&可移植性)
- 枚举
- 枚举类型的定义
- 枚举的优点
- 枚举的使用
- 联合
- 联合类型的定义
- 联合的特点
- 联合大小的计算
1.结构体的声明
1.1结构的基础知识
结构是一些值得集合,这些值称为成员变量。结构得每个成员可以是不同类型得变量
1.2结构得声明
struct tag
{
member-list;
}variable-list;
1.3特殊的声明
在声明结构的时候,可以不完全的声明
看下面这样的一个例子
一个是匿名结构体类型变量sb1,另一个是匿名结构体指针变量ps,它们的成员是一样的,那么它们的结构体类型一不一样?如果一样ps=&sb1这个操作能编译成功,我们来运行一下
注意,这里写着从 " * " 到 " * " 的类型不兼容,也就说它们的类型是不相同的
在匿名结构体类型中,当写两个一模一样的成员结构体变量,虽然成员是一模一样的,但是在编译器看来还会认为是两个不同的类型
1.4结构体的自引用
实现一个链表,就要用到结构体的自引用
数据结构--数据在内存中存储的结构
数据结构有线形数据结构:顺序表、链表;树形数据结构:二叉树
正确的结构体的自引用:下面这种形式就是链表的定义,data指的是数据域,next是指针域
struct Node
{
int data;
struct Node* next;
}
此时的结构体类型就是struct Node;(struct 不能省略掉),对结构体重新命名的时候可以省略掉struct,但是在结构体内部中指针域的名字并不能使用这个重新定义的名字,可以这样理解,重命名之后的名字在我们创建的指针域之后,在创建指针域的时候不能使用新的类型名
1.5结构体变量的定义和初始化
struct Book
{
char book_name[20];
char author[20];
int price;
char id[15];
}sb1 = {"C语言程序与设计","谭浩强",55,"THQ10001"},sb2;
int main()
{
struct Book sb2 = { "数据结构","李春葆","59","LCB10001" };
printf("%s %s %d %s\n", sb1.book_name, sb1.author, sb1.price, sb1.id);
printf("%s %s %d %s\n", sb2.book_name, sb2.author, sb2.price, sb2.id);
}
也可以不按照顺序进行初始化
1.6结构体内存对齐
计算结构体的大小
一个热门的考点:结构体内存对齐
offsetof宏-->用来计算结构体成员相对于起始位置的偏移量-->头文件为<stddef.h>
上面为S1的偏移量
下面为S2的偏移量
结构体的对齐规则:
- 1.结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处
- 2.其他成员变量要对齐到某个数字(对齐数)的整数倍的偏移处
- 对齐数=编译器默认的一个对齐数与该成员大小的较小值
- vs中默认的值为8,Linux环境默认不设对齐数,那么Linux的对齐数就是结构体成员自身大小
- 3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
同理可以得到c2的总大小
现在计算以下S3的大小
这个大小是16个字节
大小是32字节
先分析s3:
注意这里的s3:这个结构体中最大对齐数是8,所以是结构体s3对齐到8的整数倍,这个结构体的最大的对齐数是8,所以结构体总的大小是8的整数倍,故s3总的大小是16个字节
再分析s4:
c1放到相对于起始位置偏移量为0处,struct s3放到相对于起始偏移量为8的位置,并且这个s3的总大小是16个字节,最后d对齐数是8对齐到8的整数位,得到的结果是32个字节
为什么存在内存对齐?
- 1.平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处去某系特定类型的数据,否则抛出硬件异常
- 2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然界上对齐
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问权仅需要一次访问
总体来说:
结构体的内存对齐是拿空间来换取时间的做法
那在设计结构体的时候,我们既要满足对齐,又要节省空间:让占用空间小的成员尽量集中在一起
1.7修改默认对齐数
用#pragma这个预处理指令可以改变我们的默认对齐数
此时默认对齐数是1,所以根据上面的原则这个结构体的大小是6个字节
一般情况下,设置默认对齐数都是2的整数倍
1.8结构体传参
print1和print2相比print2更好一些
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降
结论:结构体传参的时候,要传结构体的地址