结构体
定义
结构是一些值的集合,这些值成为成员变量。结构的每个成员可以是不同类型的变量。
结构的声明
🎄🎄格式:
struct 结构类型
{
成员变量;
};
举例:
struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 };//分号不能丢
当然,也可以不完全声明,也就是匿名结构,不给出结构的名字。
问题:
//匿名结构体类型 struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], *p;
当两个结构都省略掉类型(标签)的时候,那么 p=&x 这个用法就是不对的。
因为编译器会把它们当做完全不同的两个类型,所以是非法的。
结构的自引用:
举例:
struct Node { int data; struct Node* next; }
记住是 类型+*,不能忽略掉*
问题:
typedef struct { int data; Node* next; }Node;
这样是可以的吗,答案是不行的,应该写成这样
typedef struct Node { int data; struct Node* next; }Node;
这里还没typedef定义完成就已经开始使用了,很明显是错误的。
结构体的定义
1,
定义一个结构体时
逐个给定值
2,
在初始化的同时给定值。
3,
在定义结构体的时候在末尾顺便加上一个个体名.
🚀🚀结构体内存对齐(重点)
光看文字很难理解,我们加上例题分析:
例题1:
🎄🎄 第一个成员变量从地址0处开始,vs中默认的对齐数是8,char类型大小是1,选择其中较小值作为对齐数,所以是1。而int类型的i的对齐数是4(类型大小或者默认对齐数的较小值),要对齐到对齐数的整数倍开始,也就是从4开始存储了,也就是最后存储完对齐的地址是8,char同理算得对齐数是1,所以对齐到地址9地方。然后结构体总大小要是最大对齐数的倍数,也就是要是4的倍数,所以自动增到12为止。
如图,最后对齐的大小就是12个字节了。
例题2:
例题2算得16.
🎄🎄第一个double对齐数算得是8,所以占用内存0~7总共八个字节,第二个char类型对齐数是1,占用第八个字节, 最后一个int从12开始占用12-15四个字节,因为对齐原则,最终要对齐到最大的对齐数8的倍数,所以是16个字节。
例题3:
由这句话可以知道s3的对齐数是8,对齐大小是16个字节。
🎄🎄c1对齐第一个字节,然后s3对齐到8开始占用,占用8~23这16个字节,d的对齐数是8,从24开始占用8个字节大小。最终占用0~31这32个字节。
🛸🛸内存对齐这么复杂,为什么要有这东西呢?难道是为了给自己添加麻烦吗?
当然不是!
首先,每个硬件平台不一定都可以随意访问任意地址上的数据,有些只能访问指定的地址处的数据,所以为了能够获取到需要的东西,设置了这个规则。
其次,内存的获取是一次四个字节,像图中第二种方式,那么获取一个int型就要获取两次,而第一种只需要一次,在性能上大大改善了。
总的来说:结构体内存的对其就是拿空间换取时间的做法。
修改默认对齐参数
我们要用到 #pragma 这个预处理指令
如图,#pragma pack(8)开始, #pragma pack()结束,这中间的句子中,就是修改了默认对齐数的。其中第一个括号中的数字就是默认对齐数。修改的数字应该为2的次方数,例如:1,2,4,8……
位段
struct A { int _a:2; int _b:5; int _c:10; int _d:30; };
其中A就是一个位段类型。
位段与结构体是类似的,但有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。2.位段的成员名后边有一个冒号和一个数字。
🛸🛸位段的内存分配
1、位段的成员可以是 int unsigned、int、signed int 或者是 char(整形家族)类型。
2、位段的空间上是按照需要以四个字节或者一个字节(char)的方式来开辟的。
3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
例子:
//一个例子 struct S { char a:3; char b:4; char c:5; char d:4; }; struct S s = {0}; s.a = 10; s.b = 12; s.c = 3; s.d = 4;
冒号后面的数字是规定使用的比特位大小。
那么空间是如何开辟的呢?
我们可以知道,10转化为二进制的代码是1010,可是 只能用3个比特位,所以取出010来存储,12二进制是1100,取用四个比特位,全部放进去,而存储的时候是四个。存储时,四个二进制为是一个十六进制位。按照这样计算,得到的数字是6、2、3、4。所以地址中的存储是,620304,也验证了我们的猜想,而且我们也可以知道,位段在一个字节中浪费的位置,下一个变量如果不够的话不会继续使用,而是开辟新的字节并在其中存储。
枚举类型
顾名思义,就是一一列举
枚举类型的定义
enum Day//星期 { Mon, Tues, Wed, Thur, Fri, Sat, Sun };
其中定义的Day就是一个枚举类型,其中一一列举了一个星期七天。{}中的内容是枚举类型的可能取值,也叫枚举常量 。这些可能取值都是有默认值的,默认从0开始,逐个+1。当然也可以在最开始给定初值,但是在外面就不能改变了,因为这是一个常量。
🛸🛸枚举的好处
明明可以用 #define 代替,为什么还要用枚举呢?
1、增加代码的可读性和可维护性。
2、和 #define 定义的标识符相比,枚举有类型检查,更加的严谨。
3、为了防止命名污染(两个变量命名相同)。因为枚举类型是封装在一个类型中,是局部变量。
4、便于调试。#define是不可调试的,在预处理阶段就已经改变。
5、便于使用,一次可以定义多个常量。
联合(共用体)
//联合类型的声明 union Un { char c; int i; }; //联合变量的定义 union Un un; //计算连个变量的大小 printf("%d\n", sizeof(un));
其中的c和i这两个变量是共用同一块内存的。un的大小为4。当然,不可能就是最大成员的大小,那样也过于简单了。
其中的变量同时只能用一个,因为会互相改变值.
依靠这个原理,我们又有了一种测试大小端的方法:
🎄🎄内存计算原则: >=最大的变量大小
是最大对齐数的倍数
比如:
![]()
其中short s[7]的大小是14,对齐数是4,所以最后大小是对齐到16。
这里可以将数组看作是七个short类型的变量。
非常感谢各位的观看!