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/