![](https://i-blog.csdnimg.cn/blog_migrate/fc220b6242294177d593cc05716e3fb5.png)
结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
1.1 结构体的声明
例如一个人的信息可以这样声明
![](https://i-blog.csdnimg.cn/blog_migrate/09a758ccfd43afd6f223da0b805cd7f6.png)
在声明结构的时候,可以不完全的声明,这种声明称为匿名结构体类型
比如一个人的信息也可以这样声明
![](https://i-blog.csdnimg.cn/blog_migrate/350d770df31102c531b627b12f067f79.png)
如果该结构体是在全局中定义的,那么xiaoming是一个全局变量。
匿名结构体只能使用一次
![](https://i-blog.csdnimg.cn/blog_migrate/b464225a40c6cbe7405628a662cd643d.png)
在上面的代码中,我们可以看到第一个匿名结构体和第二个匿名结构体的成员变量完全一样,但不能将第一个类型的变量赋给第二个类型,说明这两个结构体是完全不同的两种类型。
1.2结构体的自引用
在结构中包含一个类型为该结构本身的成员是否可以呢?
![](https://i-blog.csdnimg.cn/blog_migrate/1e971b306df9b165bd5c4600160ce186.png)
这是不可行的,我们知道每一种类型都必须有明确的大小,方便在定义变量的时候分配合适的空间。那么sizeof(struct Node)=4+sizeof(struct Node)...会进行无限套娃,这是编译器不允许的。
正确的自引用方式
![](https://i-blog.csdnimg.cn/blog_migrate/e5a26d887df9eda99829ab7c0c4bbd17.png)
因为指针的大小是由X64/X32的系统所决定的,所以这样定义编译器就可以计算出struct Node的内存大小。
我们还可以用typedef对结构体进行重命名
![](https://i-blog.csdnimg.cn/blog_migrate/42b02b0c78a6146e842700155077240d.png)
那么是否可以进行下面的命名方式呢?
![](https://i-blog.csdnimg.cn/blog_migrate/196c42c7a6dcc370fd28d7e93d10c9a5.png)
这当然是不可以的,因为在定义该结构体的时候还没有进行重命名,也就是说Node这个结构体还不存在,当然不能用来定义成员变量
1.3结构体变量的定义和初始化
结构体的成员变量有以下两种定义方式
![](https://i-blog.csdnimg.cn/blog_migrate/c7ca12484b0738147f93955a0b3ba48a.png)
当一个结构体里嵌套有另一个结构体时应使用下面的方式定义
![](https://i-blog.csdnimg.cn/blog_migrate/c6fdad89ea5387c7620d99e860fd75bd.png)
重点1.4结构体内存对齐
现在我们深入讨论一个问题:计算结构体的大小
首先要掌握结构体的对其规则
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值。
VS中默认的值为8,Linux下无默认对齐数,因此对齐数为成员自身大小值
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
不妨来举几个例子
![](https://i-blog.csdnimg.cn/blog_migrate/96e61515ce24d444c24434accd9da449.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/56d801afe34f3c6fb7fee1e859b515cc.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8eda7c33561857f41d4b84aea283b713.png)
同样,我们也可以求得S2的大小为8个字节,S3的大小为16字节
下面我们来看一个嵌套结构体的例子
![](https://i-blog.csdnimg.cn/blog_migrate/3290897430c0bce959a65c819621a1d9.png)
在结构体S4中,第一个成员c1占用大小为1byte,地址偏移量为0
第二个成员S3大小为32个byte,地址偏移量应该是S3所有成员中最大对其数的整数倍,即d的对其数8
第三个成员d大小为8byte,地址偏移量是8的倍数24
S4所占内存空间的大小应为max(1,8,8)=8的倍数,为32byte
再上一个例子
![](https://i-blog.csdnimg.cn/blog_migrate/a8e0b39b3432b73c1c142cb69a43e0b6.png)
当结构体里面有数组时,该数组成员的对齐数不是取min(默认对齐数,数组大小),而是min(默认对齐数,成员大小),我们可以把数组c看作5个char类型变量,所以S5占用的内存空间为24byte,你猜对了吗?
为什么存在内存对齐?
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
![](https://i-blog.csdnimg.cn/blog_migrate/821e299d8b746b57e0b8af3f9b013c76.png)
如上图,当使用对齐规则后,CPU一次读取4byte(X32系统下),访问A时只需要一次即可
如果没有对齐就需要访问两次
![](https://i-blog.csdnimg.cn/blog_migrate/a993dc3157340ce7bd33f3625e3da116.png)
因此内存对齐实际上是一种拿空间换取时间的做法
如果我们想尽可能多的节省空间,应该让占用空间小的成员尽量集中在一起
例如
![](https://i-blog.csdnimg.cn/blog_migrate/5e2c2b842b89cb21d02ea0f8b7cfd9b4.png)
S1和S2尽管成员变量一样,但是S2的内存空间要比S1小
1.5修改默认对齐数
这里我们使用#pragma 这个预处理指令,可以改变我们的默认对齐数。
![](https://i-blog.csdnimg.cn/blog_migrate/bc423d77669435a2ec90cd753818910b.png)
根据前面讲的对其规则计算S1,2的大小,我们可以知道S1占用12byte,S2占用6byte
从这里我们也可以得到一个简单的结论,当默认对齐数为1的时候,结构体变量的内存等于其成员的内存大小之和
1.6结构体传参
来看两个结构体传参的例子
![](https://i-blog.csdnimg.cn/blog_migrate/4fd0878042e5d7d6b204a70b2bef2f2e.png)
这里要提问一下,第一种传参方式和第二种你们觉得哪一个更好?
答案是首选第二种。
我们知道,当函数传参的时候要使用栈空间,如果直接传结构体本身无疑会消耗大量的栈帧,如果传指针的话只消耗4/8个byte即可
位段
2.1什么是位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 char 、int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
![](https://i-blog.csdnimg.cn/blog_migrate/60478167aa4947ebe5cc3e4057dff4a3.png)
位段的位指的是二进制位,即成员要占用的字节数
这个字节数不能超过该类型变量应有的大小,如int不能超过32,char不能超过8
那么位段A的大小是多少呢?
2.2位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
来看下面一个例子
![](https://i-blog.csdnimg.cn/blog_migrate/eb537cbdc5f28f7b54beec3acd9ac110.jpeg)
上图中那些标黑色0的bit会被浪费掉,故s的大小为3byte
下面来验证一下
![](https://i-blog.csdnimg.cn/blog_migrate/e13b6a03e1bcdcde895d09581dd67439.png)
对变量s取地址可以发现存储的值分别为6 2 0 3 0 4(按照16进制,一次读取4bit)
2.3位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的
拿刚才的位段A举例
![](https://i-blog.csdnimg.cn/blog_migrate/60478167aa4947ebe5cc3e4057dff4a3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ac05204985af150de8670e5ed77ea5be.jpeg)
当轮到给_d分配空间时只剩下15byte,不得不再开一个32byte的空间,那么剩下的15byte是给_d成员使用呢还是直接浪费掉并没有明确规定。但由刚才的例子可知vs环境下是直接浪费掉的
总而言之,跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
2.4位段的应用
在网络上,信息的传输是有一定格式的,所以为了节省空间,可以直接用位段来规定每个成员所占的字节数
枚举
3.1 枚举类型的定义
枚举——一一列举
![](https://i-blog.csdnimg.cn/blog_migrate/9c880d9d65233fae2333caeb113839ff.png)
上图定义的Color是枚举类型
{}中的内容是枚举类型的可能取值,也叫枚举常量
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值
![](https://i-blog.csdnimg.cn/blog_migrate/b4010fe6c1f6044d3877a522cc4994cc.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ad8a368cd2d2b5c9e369ed61ef81cc6c.png)
3.2枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
3.3枚举的使用
![](https://i-blog.csdnimg.cn/blog_migrate/95132c2ec51f0cd073777e79f816c9f8.png)
因为枚举变量实际上也是整型变量,所以枚举变量占用的内存大小为4byte
联合(共用体)
4.1联合类型的定义
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
定义方式如下
![](https://i-blog.csdnimg.cn/blog_migrate/0f97c31fc9ec7f57b56314aa761cf22d.png)
4.2联合的特点
联合的成员是共用同一块内存空间的,所以其成员变量的地址是一样的
所以成员变量不能同时使用
![](https://i-blog.csdnimg.cn/blog_migrate/5e315acdab14744ebb25b04e8b6de7a4.png)
由上图更改c 后i变为0x11223328也可以得出vs下采用小端字节序存储数据
4.3联合的大小
1.联合的大小至少是最大成员的大小。
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
如下例
![](https://i-blog.csdnimg.cn/blog_migrate/b7dbb292d7c2828eb42b49285cdd22ae.jpeg)
所以Un1类型大小应为8byte,同样可以计算出Un2的类型大小应为16byte