文章目录
1. 结构体
1.1 结构体介绍
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
1.2 结构成员的类型
结构的成员可以是标量、数组、指针,甚至是其他结构体。
1.3 结构体的声明
结构体有由关键字 struct 来进行声明,具体格式如下,下面的结构体类型是 struct tag(关键字+自定义名字)

例如描述一个学生

使用 typedef 可以对结构体类型进行重命名,如下图将 struct tag 类型重命名成 tag ,本质上 ** tag 就是 struct tag**,在创建结构体变量时更方便

1.4 特殊的声明(匿名结构体)
在声明结构的时候,可以不完全的声明(在声明结构体的时候不起自定义名字),这种声明的结构体叫做匿名结构体,匿名结构体只能在声明的时候创建变量。
比如:

注意,变量x是一个匿名结构体变量,p是一个匿名结构体类型的指针,最然这两个匿名结构体看起来一样,但是 p != &a,编译器会认为这是两种不同的结构体类型
1.5 结构体的自引用
我们知道结构体的成员可以是结构体类型,但是结构体中不能包含一个类型为该结构本身的成员(自引用)
例如:

这是因为当我们计算 sizeof(struct Node) 时,首先计算变量data 的大小,然后计算变量 next 的大小,但是结构体变量 next 里面又包含了一个结构体变量,形成套娃,无法计算大小,因此不能包含一个类型为该结构本身的成员
那我们想通过这个结构体变量访问下一个类型为该结构本身的成员该如何做呢?
我们可以在结构中包含这个结构体类型的指针变量用来存放类型为该结构本身的成员的地址
比如:

这时就可以通过指针解引用来访问下一个类型为该结构本身的成员 ,并且sizeof(struct Node) 也能够计算出来
注意:
我们知道可以使用typedef对类型进行重命名,并且可以使用结构体指针来进行自引用, 那么下面这种方式可行吗?

答案是
不行
\color {red} {不行}
不行
当我们使用匿名结构体时不能够自引用匿名结构体(类型不完整,名字不全,(这也是为什么只能在声明时创建变量)),假如我们使用 typedef 对该匿名结构体类型重命名(语法上允许重命名),并用重命名后的类型名字来自引用该匿名结构体,这时是不被允许的(语法错误),这是因为我们先声明结构体,然后再对其类型重命名。但是当我们声明结构体的时候,里面已经你有了被重命名的名字的类型,这时会显示,重命名的类型未定义(声明后再重命名),这就成了先有蛋还是先有鸡的问题了。如果是正常的结构体也是不能用重命名后的类型名字来自引用该匿名结构体
解决方案:

- 避免使用匿名结构体
- 使用原本的结构体类型(关键字+自定义名字)来进行自引用
1.6 结构体变量的定义和初始化
- 结构体变量的定义有两种方式:
(1)在声明结构体时创建

(2)定义创建结构体变量

注意:
如果使用 typedef 对结构体类型重命名,那么定义创建结构体变量可以这样写

这种写法的好处是当自定义名字过长时,可以重命名成简洁的名字方便书写
- 结构体变量的初始化有两种方式
初始化时有两种方式进行赋值,一种直接赋值(只能按顺序赋值),另一种是使用 (.) 点操作符访问结构体成员进行赋值(可以不按顺序)
(1)声明类型、创建变量同时初始化


(2)定义变量的同时赋初值


1.7 结构体内存对齐(计算结构体大小)
首先得掌握结构体的对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数) 的整数倍的地址处。
对齐数=编译器默认的一个对齐数与该成员大小的较小值。
VS中默认的值为8
Linux中没有默认对齐数,对齐数就是成员自身的大小
3.结构体总大小为最大对齐数(每 个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
注:offsetof() 可以计算结构体成员相较于结构体起始位置的偏移量
-
无嵌套结构体

-
嵌套结构体
-
为什么存在内存对齐?
大部分的参考资料都是如是说的:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定的数据类型,否则抛出数据异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。

在设计结构体的时候,如果我们既要满足对齐,又要节省空间,可以用空间小的成员尽量集中在一起
1.8 修改默认对齐数
使用 #pragma 预处理指令修改默认对齐数

1.9 结构体传参

注意:
我们在进行结构体传参的时候,最好传构体的地址
\color {red} {我们在进行结构体传参的时候,最好传构体的地址}
我们在进行结构体传参的时候,最好传构体的地址
因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
2. 位段
用结构来实现位段
2.1 什么是位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
比如:这里的A就是一个位段类型

2.2 位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
-
以VS平台为例(仅保证VS平台)
在VS平台上数据是从右向左存的,并且当这次申请的剩余空间不能够存放下一个成员时,会向内存申请新的空间来存放,原来剩余的空间被浪费。

2.3 位段的跨平台问题
- int位段被当成有符号数还是无符号数是不确定的(C语言中并没有明确规定为位段类型中int开辟的空间是被当成有符号空间还是无符号空间)。
2.位段中最大位的数目不能确定。(16位机器 int是16bit(2字节),32位机器int是32bit(4字节),如果在32位平台 int 写成27,在16位机器会出问题。
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时, 是舍弃剩余的位还是利用,这是不确定的。
2.4 位段的应用

3. 枚举
3.1 枚举的定义

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫枚举常量。
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
例如:

3.2 枚举的优点
为什么使用枚举?
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
3.3 枚举的使用

4.联合(共用体)
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
4.1 联合类型的定义

4.2 联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。


由此特性,我们可以使用联合体来判断大小端存储

注意,因为联合体的成员共用同一块空间,因此在同一时间只能使用一个成员,这是因为当 i 值时,如果改变 c 的值,i 的值就会跟着改变,同理,改变 i 时,c 也会跟着改变

当我们开启调试看内存时,先将 un.i 进行赋值

此时可以看到 un.i 的值为 0x11223344
再给 un.c 赋值

可以看到第一个字节变成了0x55,说明赋值成功,但是这样 un.i 的值就变成了 0x11223355,所以在同一时间只能使用一个成员。
4.3 联合大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

1651

被折叠的 条评论
为什么被折叠?



