2020-C++高级程序设计-C++ 指针与引用
C++ 指针
- C++中的指针主要是管理地址信息
- 管理数据
 - 调用代码
 
 
1. 指针定义与基本操作
- 定义:
<基类型>*<指针变量>:void*:可以作为所有指针的接口,void的指针类型可以被赋值为任何类型的指针。 
1  |  | 
- 使用typedef来定义一个指针类型(别名)
 
1  |  | 
- 可以直接进行赋值:因为C++可以进行系统开发,所以一定是可以操作绝对地址的。
int *p = (int *)0x080483A0;int *p = 0x080483A0;
 
1.1. 基本操作
- 取地址:
& - 间接取内容:
* 
1  |  | 

- 所有的指针都要初始化(Pointer Literal)
- C++会初始化指针为0(默认初始化),如果编译器发现指向为0,则报错,因为0地址是保留空间
 - 不允许:
char *p = (void*) - 在新的C++部分中,我们引入了
nullptr:作为不依赖任何值的指针。Pointer p = nullptr; 
 
1  |  | 
- 空指针并不一定用与整数0同样的二进制模式表示,可由实现者采用任何选定的方式表示。
 - 赋值:同类型赋值:
p = &d//error,不同类型 - 加减:整形
- 结果类型:不变
 - 数值:sizeof(基类型) * 整形数值
 - char*是一个一个走
 
 
1  |  | 
1.2. 指针之间的运算
- 同类型指针相减(仔细看offset的定义)
- 结果类型:整形
 - 数值:偏移量
 
 
1  |  | 
- 同类型指针比较:
- == 或者 !=
 - 一般不使用 > 等符号
 
 
1  |  | 
- void*
- 只管理地址信息
void *p; - 是指针类型的公共接口
 - 任何操作须做强制类型转换(不然是没有意义的)
 
 - 只管理地址信息
 
1  |  | 
- 指针可用来将某块内存清零
 
1  |  | 
1.2.1. memset()的部分具体解释
- 通常是为申请内存进行初始化的操作
 - 可以将int数组的空间初始化为0或者-1
 - 函数原型:
memset(void *s,int ch,size_t n); 
1  |  | 
1.3. 常量指针与指针常量
- 操作地址一定要保证存在并且有意义
 
1.3.1. 常量指针
const <类型> * <指针变量>- 不可以修改指针指向单元的内部的值
 
1  |  | 
- 常量指针指向的地址存储的值不可以被修改,用来消除函数的副作用,保证在函数端中只读数据。
 - cp(variable) -> c(constant)
 - 服务提供者Use const whenever possible(cp = &y可以保证函数不修改参数中的值):让调用者直接访问被调用者空间中的数据,为了保证不可以修改数据,使用const
 
1  |  | 
- 面向对象中没有const会带来很大的访问权限的问题
 
1.3.2. 实例说明指针
1  |  | 
1.3.3. 指针常量
<类型>* const<指针变量>- 在定义时初始化
 - p(constant)->x(variable)
 
1  |  | 
- const int * const p是非常强的指针约束
 
2. 指针与函数
- 指针作为形参
- 提高传输效率
 - 函数副作用
 - 常量指针
 
 - 程序基本组织单位就是函数
 - 进阶:Function Pointer指向函数的指针
 
1  |  | 
- (*fp)就是函数的执行
 
2.1. 函数指针实现框架(如何写一个框架)
- 一个计算任务的执行(加法/减法)
 - 是一个前缀输入
 
2.1.1. 第一版:高耦合版本
1  |  | 
2.1.2. 第二版:剥离IO部分
1  |  | 
2.1.3. 第三版:抽离计算部分
1  |  | 
- 此时发生修改,我们只需要修改枚举类型和函数类型
 
2.1.4. 最后一版:主方法集成
1  |  | 
2.2. 函数指针实现泛型
2.2.1. 冒泡排序第一版:默认int型排序
1  |  | 
2.2.2. 冒泡排序第二版:扩展复杂数据类型
- 每一个数据块的大小可能是不确定的,所以我们需要确定每一个块的大小(width)
 - void * base对应首地址
 - 解决序关系的处理
 - 解决数据块的交换
 
1  |  | 
2.2.3. 冒泡排序第三版:使用泛型函数实现调用部分
1  |  | 
2.2.4. 冒泡排序另一种实现:简单数据类型
1  |  | 
2.2.5. lambda表达式
直接给出即可
2.3. 函数指针
- 计算一元函数在某区间上的定积分
 
1  |  | 
2.3.1. 一维数组

- 注意右侧的第二个部分:可以控制p的移动情况
 *(p+i):p不移动*(p++):p移动int *p = a:这时候a表示的是数组的首地址- 这里传递的是
int * const - a[0]可以写为p[0]
 
- 这里传递的是
 
1  |  | 
sizeof(a):是数组的整个块的大小sizeof(a[0]):是数组中一个元素的大小
2.3.2. 二维数组

- 二维数组用一维方式访问
 int *p = &a[0][0]:p指向的是T类型
1  |  | 
1  |  | 
3. 指针与数组
- 数组元素操作:下标表达式和访问效率
- a[i] == *(a+i)
 - &a[i] == a+i
 
 
1  |  | 
- 多维数组
 
1  |  | 
- 通过指针和数组元素存储的关系来快速访问数组元素
 
3.1. 降维操作
- 越界操作:C++认为是允许的,只要这块内存空间在我们的控制范围内即可
 
1  |  | 
3.2. 升维操作(重要)
- 因为申请内存空间的时候只能申请到线性部分
 
1  |  | 
3.3. 指针数组
- 
main函数:
int main(int argc,char * argv[],char * env[])- argc:参数个数(包含命令)
 - argv:命令行参数
 - env:环境参数(为什么这个不必指出长度?因为\0结束,一个结束符)
 
 - 
Eg.
 
1  |  | 
- 数组中的元素为指针(以下两种方式实现是不同的:内存空间的分配)
 
1  |  | 


3.4. 可变参数
int printf(const char*,...):后面是可变参数,由调用者决定。const char*:是调用者和被调用者之间的约定

- printf("%d%c",x,y);
- 少写一个也没问题
 - 这种约定是不受保护的,给出参数个数和类型,表示如何取
 - active frame:之前的active frame地址要保存下来
 
 
3.5. 实现Myprint
- alignment的说明(内存地址)
 
1  |  | 
- 具体的内存C++实现
 
1  |  | 
- 格式化串攻击:偷摸摸搞到其他部分的内存
 
4. 指针与结构
- 结构成分的访问:
(*p).x == p->x - 结构作为函数参数:
- 大块数据传输
 - const
 
 
5. 多级指针
- 基类型为指针类型
 - 指向指针的指针
 - 编写一个函数交换两个字符串
 
1  |  | 
6. 动态变量
- 动态:
- 大小
 - 生命周期
 
 - 非编译时刻确定
 - 是在heap中申请存储空间
 
6.1. 申请动态变量
new <类型名> [<整型表达式>]- malloc也可以用来申请动态变量(但是建议使用new)
 - new和malloc两者区别:
- 语法:强制类型转换
 - 语义:构造函数
 
 - 申请内存的时候有可能会申请失败:
- new之后一定要判断p是不是NULL
 - 如果不是NULL,一定是有效的
 
 
1  |  | 
6.1.1. 使用malloc分配空间
1  |  | 
6.1.2. 分配连续空间(涉及多维数组)
1  |  | 
6.1.3. 面向对象中的new关键字
1  |  | 
6.2. 归还动态变量
- 操作符:
new -- delete|delete[]- delete:调用数组内第一个对象的析构函数
 - delete[]:调用数组内所有的对象的析构函数
 - 空间都会被归还
 
 - 操作符:
malloc -- free- free不会调用析构函数。
 
 - 如何处理归还的大小(cookie):在数据的前面会加入一个size:这也就是为什么我们一定要复制指针,然后归还地址归还的是原地址。
 
1  |  | 
- 由于C++没有GC,所以要防止memory leak
- 析构函数:不仅仅是归还自己的内存,还有窗口资源和文件等东西归还掉。
 
 
6.3. 动态变量的应用
- 数据结构:
- 链表(单、双) --栈、队列
 - 树、图
 
 - 链表的结点的定义
 
1  |  | 
- 具体应用:硬盘上的文件存放:一种实现是单链表
- 文件分配表FAT:用来存储数据的开始的位置。
 - FAT一旦被破坏就导致所有的数据丢失
 
 
6.4. 单链表 - 应用
6.4.1. 单链表的插入
1  |  | 
head是不可以动的
- 表头进行插入
 
1  |  | 


- 表尾进行插入
 
1  |  | 

- 表中间插入:插在链表中某结点(值为a)的后面
- 短路表达式:如果部分子表达式的值已经能确定表达式的值,则其他部分不会进行计算
 
 
1  |  | 
- 表中间插入:插在链表中某结点(值为a)的前面
- 链表永远不为空(永远不发生在头的插入)
 - Guard node:(一个Dummy结点在最前面)
 
 
1  |  | 
6.4.2. 单链表的删除
- 删除值为a的链表结点
 
1  |  | 
6.5. 单向排序链 – 应用
- 结点定义
 
1  |  | 
6.5.1. 释放单向排序链
1  |  | 
6.5.2. 打印单向排序链
1  |  | 
6.5.3. 插入单向排序链
1  |  | 
6.5.4. 删除单向排序链
1  |  | 
7. C++引用
- 定义:为一块已有的内存空间取一个别名
- 引用变量和被引用变量,必须是同类型
 - 引用变量定义中的&不是取地址操作符
 - 定义引用变量时,必须初始化
 
 
1  |  | 
- 应用:
- 函数参数传递
 - 动态变量命名
 
 - 函数返回值为指针或者引用
- 不可以返回局部量
 - 涉及到操作符的重载
 
 
1  |  | 
- 用 const 限定引用
void swap(const int& a, const int& b) - 引用一旦定义,不可被改变,可以被const限制
 - 及时释放在堆中的变量的引用
 
1  |  | 
2020-C++高级程序设计-C++ 指针与引用
      https://spricoder.github.io/2020/07/01/2020-C-plus-plus-advanced-programming/2020-C-plus-plus-advanced-programming-C++%20%E6%8C%87%E9%92%88%E4%B8%8E%E5%BC%95%E7%94%A8/