自定义类型
1. 结构体类型(struct)
1.1 结构体简介
什么是结构体呢?
结构说一些值的集合,这些值被称为成员变量。这些成员变量可以是不同类型的。
这里对其他的结构体基础知识不做过多地阐述,直接跳到小编认为重要的地方。
1.2 结构体内存对齐
结构体的内存对齐,对于我们来说,就是用来计算结构体的大小。
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n",sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
在这里,两个结构体类型的大小为何不一样?
因为结构体成员不是按照顺序在内存中连续存放的,而是有一定的对齐规则:
1.结构体的第一个成员永远放在相较于结构体变量的起始位置的偏移量为0的位置;
2.从第二个成员开始,往后的每个成员都要对齐某个对齐数的整数倍处.如果没有对齐数,那对齐数就是结构体成员变量自身的大小;
对齐数:结构体成员自身的大小和默认对齐数的较小值
VS上默认对齐数是8。
3.结构体的总大小,必须是最大对齐数的整数倍;
最大对齐数:所有对齐数中的最大的值
4.如果在结构体中嵌套了结构体,则对齐到自身最大对齐数的整数倍。
1.2.1 结构体S1内存大小分析
通过这些规则,我们来分析一下上面两个结构体
1.c1在内存上,先放在了偏移量为0的位置,并且占据一个字节单位;
2.i在内存上,其自身大小int类型为4,VS上默认对齐数是8,两个中的较小值为4。所以i的起始位置要对齐到4的整数倍上,也就是4上,并且占据4个字节;
3.c2同上,其自身大小char类型为1,VS上默认对齐数是8,两个中的较小值为1.所以c2的起始位置要对齐到1的整数倍上,也就是8上,并且占据1个字节.
4.最后,结构体S1的总大小,必须是结构体成员最大对齐数的整数倍,也就是4的整数倍。由于S1已经占据了0~8这9个字节,所以S1的大小就为12个字节。
1.2.2 结构体S2的内存大小分析
1.c1在内存上,先放在了偏移量为0的位置,并且占据一个字节单位;
2.c2在内存上,其自身大小char类型为1,VS上默认对齐数是8,两个中的较小值为1。所以i的起始位置要对齐到1的整数倍上,也就是2上,并且占据1个字节;
3.i在内存上,其自身大小int类型为4,VS上默认对齐数是8,两个中的较小值为4。所以i的起始位置要对齐到4的整数倍上,也就是4上,并且占据4个字节;
4.最后,结构体S2的总大小,必须是结构体成员最大对齐数的整数倍,也就是4的整数倍。由于S2已经占据了0~7这8个字节,所以S2的大小就为8个字节。
1.3 修改默认对齐数
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
在这里就不测试了,大家可以自行测试!
1.4 结构体传参
直接上代码:
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。 原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
结论: 结构体传参的时候,要传结构体的地址。
2. 枚举类型(enum)
枚举顾名思义就是------列举;
2.1 枚举类型的定义
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
以上定义的 enum Day , enum Color 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。
2.2 枚举的值
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。 例如:
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
枚举的优点
我们为什么要用枚举呢?
- 增加代码的可读性和可维护性;
- 和#define定义的标识符比较枚举有类型检查,更加严谨;
- 防止了命名污染(封装);
- 便于调试;
- 使用方便,一次可以定义多个常量;
3. 联合体类型(union)
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
3.1 联合体的定义
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
3.2 联合体的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
union Un
{
int i;
char c;
};
union Un un;
int main()
{
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
return 0;
}
两个成员的地址是一样的,共用同一块内存空间。
3.3 联合大小的计算
1.联合的大小至少是最大成员的大小。
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
Un1、Un2对齐4的倍数。