正文开始:
1.结构体
1.1 结构体的基础知识
结构体变量是一群不同的变量用来形容一个物体的一个组合变量,就像是我们描述一个学生,会描述他的姓名,年龄,学号,成绩等等,然而这些东西不是一个普通的变量就能全部描述的,我们就引进了结构体变量的概念
1.2 结构体的声明
- 举个栗子
struct Stu
{
char name[20];
int year;
int number;
float score;
}stu1;
从上面的例子,我们可以总结出
- 结构体声明的大致模板为:
struct tag//tag是我们定义的结构体的类型名称,struct是结构体的关键字
{
member-list;//成员列表,我们可以在内部自定义我们需要的变量类型
}variable-list;//这里的varibale-list可以省略,可以在我们需要使用的时候再进行定义
1.3 特殊的声明
- 结构体在声明时可以省略掉tag,但是此时的variable-list就不能省略了,不然声明的这个结构体就用不了了
- 对于匿名结构体,即使两个匿名结构体的成员变量完全一致,并且顺序都完全一致,也是不能把这两个结构体看作是同一个结构体
1.4 结构体的自引用
- 在结构体中包含的一个类型为结构体本身的成员是不可以的,这样会出现问题,譬如sizeof(该结构体)不知道咋整了,最合理的是包含该结构体类型的指针,这就要涉及到链表的知识了,这里先不讲,留到后面再详细介绍
- 结构体类型声明后想要定义一个对应类型的变量要写好大一串,如果不想的话,我们可以用到typedef
- 举个栗子
typedef struct Node
{
int data;
struct Node* next;
}Node;
- 此时分号;前面的就不是变量了,而是重命名的struct Node,后面想要定义struct Node类型是变量就可以将变量类型直接写成Node了
1.5 结构体变量的定义和初始化
- 有了上面的结构体声明,再定义结构体变量就是很容易的事情了,就把结构体的声明当成一种数据类型,定义变量就和其他的普通变量定义是一样的,不过初始化就有所差异了
- 初始化变量一般在定义时就会进行,因为结构体变量里面包含了多个不同类型的变量,所以在初始化时,先要用{},再将结构体变量的每一个成员变量的初始值给出,每个值用,隔开
- 举个栗子
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//定义变量的时候进行初始化
1.6 结构体内存对齐
- 既然结构体变量是一种变量,自然也就有大小,但是结构体变量的大小不是能直接看出的,要通过内存对齐规则来进行计算
- 先上文字描述
- 结构体变量的第一个成员变量放在跟结构体地址偏移量为0的地址处
- 接下来的变量需要比较对齐数与自身的大小,以两者中较小的作为对齐数,也就是从第二个变量开始,内个变量存放之前都要确定它的具体存放位置,而不是单纯的跟在上一个变量的结束位置处,变量存放的位置偏移量要是对齐数的整数倍,夹在两个变量存储内置未被使用的字节就被浪费掉了(VS的默认对齐数为8,Linux没有默认对齐数)
- 结构体变量的大小总是最大对齐数的整数倍
- 如果嵌套了其他结构体变量,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的大小就是所有最大对齐数(含嵌套的结构体的最大对齐数)的整数倍
- 举个栗子
代码实现如下
总的来说,结构体的内存对齐就是典型的拿空间换时间的做法
1.7 修改默认对齐数
#pragma pack(4)//修改默认对齐数为4,一般来说,默认对齐数一般修改为2的整数次幂
#pragma pack()//取消设置的默认对齐数,还原为默认值
//在默认对齐数不合适的时候,可以修改默认对齐数
1.8 结构体传参
- 这个涉及到压栈的问题,后面会专门写一篇文章讲相关内容,这里先跳过~
- 总的来说就是结构体传参要传结构体的地址
2.位段
2.1 什么是位段
- 位段的声明是与结构体类似的,但是它的成员变量只允许是int、unsigned int、signed int和char
- 位段的成员变量后面有一个冒号和数字
-
举个栗子
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
那么,struct A 的大小是多少呢?
2.2 位段的内存分配
按照上图的分析,那么这个位段的大小就应该是8,事实是不是这样呢?
代码实现如下
- 结果跟我们所分析的是一样的,但是位段涉及很多不确定因素,这只是在VS下完全符合我们的分析
2.3 位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在
3.枚举
所谓枚举,顾名思义就是将所有的可能性全部一一列出,例如月份,星期几,性别,颜色等等
3.1 枚举类型的定义
enum Color//颜色
{
RED,
GREEN,
BLUE
};
- {}中的可能取值被称为枚举常量
- 这些可能值是有值的,一般默认从0开始,而后的依次递增1,同时,这些可能指在被定义时可以给它赋初值
- 举个栗子
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
3.2 枚举的优点
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
3.3 枚举的使用
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5; //这样的非法的,不能将int类型的数据赋给枚举类型
4.联合(共用体)
联合体和结构体一样,包含一系列的变量,但不同的是,联合体里面包含的变量共用同一块空间(因此也被称为共用体)
4.1 联合体类型的定义
//联合类型的声明
union Un
{
char c;
int i;
};
//实际上变量c和变量i是放在内存中的同一位置的
4.2 联合的特点
- 联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
4.3 联合大小的计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
- 举个栗子
union Un1
{
char c[5];//占用内存字节为5,5<8,所以对齐数为5
int i;//占用内存字节为4,4<8,所以对齐数为4
};
union Un2
{
short c[7];占用内存字节为14,14>8,所以对齐数为8
int i;占用内存字节为4,4<8,所以对齐数为4
};
printf("%d\n", sizeof(union Un1));//最大对齐数为5,就是5了
printf("%d\n", sizeof(union Un2));//最大对齐数为8,16是8的整数倍
以上就是本篇的所有内容啦,欢迎各位uu们的指正和建议~