自定义类型
结构体
**结构:**结构是一些值的集合,相较于数组是某种类型的集合,结构体一般都是不同类型的集合来用于描述现实中的某个复杂对象
结构体的组成
struct tag
{
member-list;
}variable-list;
- struct:结构体的关键字
- tag:结构体的标签名
- struct tag合起来称为结构体类型类似与int,char…
- member-list:结构体成员列表
- variable-list:变量列表,在声明结构体的同时定义全局变量
一种特殊的结构体:
匿名结构体:不写结构体标签的结构体类型
struct
{
member-list;
}variable-list;
结构体变量的定义
- 在声明结构体的同时定义全局变量
- 单独定义一个全局变量
- 定义一个局部结构体变量
struct Book
{
int Id;
float price;
}a;//在声明结构体的同时定义全局变量
struct Book b;//单独定义一个全局变量
int main()
{
struct Book c;//定义一个局部结构体变量
return 0;
}
结构体的初始化
结构体的初始化类似于二维数组的初始化,二维数组是将每一行的元素用{}括起来初始化,结构体是将每一个成员变量用{}括起来初始化
struct Book
{
int Id;
float price;
};
int main()
{
struct Book b = { {1314},{12.7} };
printf("%d", b.Id);
return 0;
}
结构体的访问
结构体的访问分为:
- 通过结构体变量名.来访问
- 通过结构体指针->来访问
int main()
{
struct Book b = { {1314},{12.7} };
struct Book* p = &b;
printf("%d\n", b.Id);//通过结构体变量名.来访问
printf("%d", p->Id);//通过结构体指针->来访问
}
结构体的内存对齐
内存对齐的规则:
- 结构体第一个成员在内存中应放在为结构体变量开辟内存偏差为0的地址处
图解:
- 对于一个结构体:
struct S1
{
char c1;
int i;
char c2;
}s;
其第一个成员在内存空间的位置:
- 从s1指向位置开始给struct S1 s开辟一片空间,红色部分则为第一个成员的存储空间(设每一格为一字节),即第一个成员在所开辟空间的存储位置距其实位置的偏移量为0
2.其他成员在存储时应存放在对齐数的整数倍数的偏移量的地址处
对齐数:编译器默认的对齐数和成员大小的较小者,若编译器没有默认的对齐数则就为成员大小
图解:
- 对于结构体变量struct S1 s中的成员int i来说他的大小是4个字节,VS编译器的默认对齐数是8个字节,两者取其小,则该成员的对齐数为4
- 对于成员int i来说在存储时应存放在对齐数的整数倍数的偏移量的地址处,即偏移量为4的倍数的地址处,也就是黄色部分
3.结构体的大小应为所有成员中最大对齐数的倍数
图解:
- 对于结构体变量struct S1 s其成员在c2处便全部存入内存此时占9个字节,但由于结构体的大小应为所有成员中最大对齐数的倍数,在该结构体中最大的对齐数为4,因此还需多开辟3个字节补齐
4.对于结构体中嵌套的结构体,其对齐数为嵌套的结构体中的最大的对齐数
例如:
struct S1
{
char c1;
int i;
char c2;
}s;
struct S2
{
char c1;
int i;
char c2;
struct S1 s;
}s1;
- struct S1 s的对齐数为其自己最大的对齐数
内存对齐的作用:
- 提高代码的可移植性
- 提高访问速度
- 总的来说内存对齐就是牺牲一部分空间来换取时间
在设计结构体的时候如何能否满足即使内存对齐又尽量牺牲少的空间呢?
- 让内存占用小的成员尽量挨在一起
修改默认对齐数:
VS的默认对齐数是8,我们可以通过**#pargma pack(期望对齐数)**来修改默认对齐数
#include <stdio.h>
#pragma pack(2)//设置默认对齐数为2
struct S1
{
char c1;
int i;
char c2;
};
- 一般我们修改的对齐数应为2的n次方
结构体传参
结构体传参分为:
- 传值传参
- 传址传参
-
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
-
因此结构体传参首选传址传参
结构体实现位段
位段和结构的声明极其类似,只有两个不同
- 结构体成员必须是int、unsigned int 或者char类型
- 位段的成员名后边有一个冒号和一个数字
例如:
struct A {
int a:2;
int b:5;
int c:10;
int d:30;
};
位段的内存分配:
- 位段的内存分配是按照4个字节(int),或者1个字节(char)来开辟的
- 位段成员是以比特位来消耗所开辟的空间的
- 位段的内存分配并没有标准化规定,因此有较差的移植性
- 成员a使用4个字节中的2个比特位,成员b,c也使用相同4字节的15个比特位。但在使用时c语言并没有明确的规定是从高地址到低地址的使用还相反,因此在移植时可能会出现差异。
- 成员d申请30个比特位的空间,但由于之前a,b,c已经使用了4个字节的17个比特位,剩余的空间不足以容纳成员d,因此再申请4个字节来使用,但之前4个字节剩余的空间是否还需要使用c语言也没有给出明确的规定
总的来说:
- 位段就是牺牲可移植性来换取最大空间的利用率
枚举
- 枚举就是给其所定义的类型列举所有可能的值
- 枚举可以一次定义多个常量
枚举类型的定义
- 枚举常量的值依次加1
- 枚举成员间用 , 隔开
- 可以给枚举成员初始化值
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
- 枚举成员在内存中按照整形存储,枚举成员从上到下由0开始按1递增,若遇上有初始化的枚举成员,则初始化枚举成员之后值随着该制递增
例:
枚举常量的优点
- 便于调试
- 一次定义多个常量
- 枚举的常量更加易于调试,枚举常量在调试时依旧是枚举成员名,方便观察,而通过#define定义的常量由于在预编译的阶段就已经完成了替换因此在程序运行时便无法直观的看见#define定义的常量名
联合体
- 联合体也可称之共生体,即几个类型共同使用同一块内存空间
联合体的声明
- 联合体的声明,初始化,以及成员的访问和结构体基本一致
union Un
{
char c;
int i;
}u;
- c和i在内存中使用的同一块空间的内存
- 有颜色的地方代表在内存中为union Un u开辟的空间,黄色区域为c所使用的空间,黄色和蓝色区域为i所使用的空间。c和i有很明显的共同部分,因此在修改其中一个的值的时候很有可能会修改到另一个的值
- 因此在使用联合体的时候每次最好只使用其中的一个成员
联合体的大小
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。