本篇文章主要介绍三种自定义类型,分别是:结构体、联合体、枚举。
一.结构体
1.结构体类型的声明
直接举一个例子:
//一本书
struct s
{
char name[10]; //名称
char a; //作者
int p; //价格
};
2.特殊的声明
结构体也可以不写结构体标签,写一个匿名结构体。这种结构体如果不重命名的话,基本就能使用一次。
//还是这个书本的例子
typedef struct
{
char name[10]; //名称
char a; //作者
int p; //价格
}book;
这里对他进行了重命名。
3.结构体的自引用
这个学过链表都不陌生。就是在结构体内在引用一下自身的结构体。看一下例子:
//一个节点
struct Node
{
int n;
struct Node* next;
};
4.结构体内存对齐(重点)
结构体内存对齐主要解决的是结构体大小的问题。
4.1对齐规则
首先我们要了解结构体的内存的对齐规则:
-
结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处;
-
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较小值;
PS.VS的默认数是8,Linux没有默认数。
-
结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的 整数倍;
-
如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
举个例子:
struct s
{
char a;
int b;
char c;
};
首先a占据一个位置(规则1);b是int类型,占四个字节,根据规则2,对齐到整数倍的位置,也就是如图所示的蓝色位置;c按照同样的道理放到黄色的位置;最后整个结构体的大小根据规则3来判断,是4的倍数,所以黄色后面有三个位置是空的。
对第4条规则的解释:
struct s
{
char a;
int b;
char c;
};
struct ss
{
struct s s1;
int a;
};
ss中嵌套了一个s,s的对齐数是s中对齐数的最大值也就是4,s在ss中所占空间大小是它本身的大小也就是12。
4.2为什么要进行内存对齐?
从参考资料大致可以得到以下两点:
-
平台原因 (移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
-
性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总的来说,结构体内存对齐就是拿空间换时间的做法。以后我们如果在某些特定条件下可以使用这个规则来定义结构体。
4.3修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对齐数。
#pragma pack(1)//将默认对齐数修改成1
#pragma pack()//还原回默认对齐数
5.结构体实现位段
这里简单的介绍一下位段:1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型。 2. 位段的成员名后边有一个冒号和一个数字。
位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。 所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入 放在一个变量中,然后赋值给位段的成员。
二.联合体
1.联合体的简介
联合体,又名共用体,即所有成员公用一块内存空间。如果冒然给联合体的一个成员赋值会改变其他成员的值。
//对联合体的声明
union Un
{
char c;
int i;
};
2.联合体大小计算
1.联合的大小至少是最大成员的大小;
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
三.枚举
1.枚举类型的声明
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
2.枚举的优点
-
增加代码的可读性和可维护性;
-
和#define定义的标识符比较枚举有类型检查,更加严谨;
-
便于调试,预处理阶段会删除 #define 定义的符号;
-
使用方便,⼀次可以定义多个常量;
-
枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用。