当前课程知识点:基于Linux的C++ > 第七讲 指针与引用 > 7.8 动态存储管理(二) > LinuxCPP0708
在C++里面呢
它提供了new、delete操作符
来帮你做内存分配和管理任务
这个功能要比malloc和free要强大得很多
它事实上不仅能够替你分配内存
还能够替你构造所分配的
那个目标数据对象
先看创建单个这个目标数据对象的话
你就用new这个操作符
你比如说int * p定义整数的一个指针
我想分配单一的一个整数
那么p就赋值为new int
就给你分配一个新的整数存储空间出来
然后你就可以用*p赋值为10
把10放到它那个新分配好的
存储空间里面去
你也可以写new(int)
把一个new当做一个函数一样
那这个函数很特殊
括号里面带的是一个类型 可以的
new(int) 把它创建出来——
一个新的整数的存储空间
加括号也是可以的
你还有可能
分配这个数据对象的同时初始化
实际上把这两条语句合并成一条
你也可以这么写int *p之后
p赋值为new int(10)
分配一段存储空间让p指向它
然后把*p设成10
事实上这两条语句你都可以还合成一行
int *p = new int(10)也是可以的
这个时候int还可以继续加括号
就变成new(int)(10)
注意不是一个括号里面用逗号分隔开
还是两个括号new(int)(10)
可以按照这个方式来写
这就是分配一个单一的目标数据对象
用new操作符
如果你想分配一个数组
就是很多个数据对象
那么你就要用new[]操作符
int *p 然后p赋值为new int[8]
这个时候我将分配
8个元素的整数数组
然后把整数数组的基地址赋值给p
这个操作符的名字叫new[]
但实际上在写的时候new和中括号中间
一定要填上空格和我们元素类型的名字的
你应该这么写
你如果使用C++的new、new[]这两个操作符
来分配我们的数据——目标数据对象
那么当你不要它的时候
就应该使用delete和delete[]操作符
当你要释放单个目标数据对象的时候
delete p销毁它就行了
注意 销毁的仍然不是p
这个指针数据对象本身
仍然是p所指向的那个
动态分配出来的目标数据对象new int
如果是一个数组
那么释放它的时候就应该delete[] p
按照这个方式
去销毁多个目标数据对象
正常情况下 你使用的时候要记得
malloc和free是配对使用的
new和delete是配对使用的
new[]和delete[]是配对使用的
是不能搞混淆的
尤其是C的格式的分配的内存
一定要用C的格式去销毁它
C++的格式分配的内存
一定要用C++格式去销毁它
两者是不能够混用的
因为它们做的事情是不尽相同的
指针为什么难用呢
它涉及到很重要的一个地方
就这里面涉及到两个数据对象
一个是目标数据对象
还有一个指针数据对象本身
尤其是当目标数据对象本身
它是没有名字的时候
那么这种情况下边
访问它的唯一一个手段
就是需要通过一个指针数据对象来指向它
这种时候我们可以称之为这个指针
拥有那个目标数据对象的所有权
问题就在于我们编程的时候
我们有可能很多个指针
比如两个指针
指向同一个目标数据对象
那么这种情况下边谁拥有它
谁有权使用它 一个是有使用权
一个不仅有使用权还有所有权
我们就要决定它
但是我们程序中呢
其实也没有什么很好的方案
能够解决这个问题
尤其是在C/C++早期的代码里边
这个东西是没有办法解决的
你只能是我们的程序员
头脑中要很清晰地架构
这个目标数据对象是谁所有的
谁只有使用权而不是拥有权
这样的话才能够保证你的程序
不容易写错
否则的话是非常容易出错的
所以指针使用的时候
有一个一般性的规则
我把它提炼出两个使用规则
一个叫主动释放原则
你通过一个指针动态分配一段内存
你只使用它 这个没问题
只要内存有 那么你就可以做到这一点
但是你不用了
你就应该主动地去释放它
可是我们有的很多同学
他一开始写的时候不注意这个问题
只记得分配了 忘了还
正常情况下你的程序只要内存够
一般你也看不出多少问题来
但如果内存不够 你借了不还系统就完了
还有一种情况呢
就是你程序真结束了
你真地所有东西真不还
那么操作系统全部都会拿回去
所以当你的内存足够的时候
这个倒关系不大
一个优秀的程序员一定要记得
这个内存你分配了 要主动地释放
第二个就是所有权转移原则
如果你一个函数通过malloc也好
通过new也好 动态分配了一段内存
后来这个函数退出了 它结束了
但是这个内存呢你还要用
函数都结束了
但是内存不能够被释放
这个时候你就要特别注意了
因为那个内存区域是这个函数中的
某一个局部变量所拥有的
它有所有权 但是那个变量
将会随着这个函数一样死掉了
所以这个所有权必须在这个函数
结束之前要能够完成它的转移
往往都是通过函数的返回值
转移给我们的主调函数
所以在这个时候 在我们这个函数里边
是只有malloc没有free
因为那个动态分配的存储空间
并没有被释放
所有权转移给主调函数了
在编写指针类型的程序的时候
经常会出现两种问题
一个就是空悬指针的问题
它就是很典型的一种情况就是
所有权的重叠所导致的
如果你有两个指针
通过这一个指针来分配一段存储空间
后来你把这个指针又赋值给另外一个指针
这就意味着两个指针
都指向同一个目标数据对象
那么如果你的脑子不清醒
就有可能认为这两个指针
都拥有目标数据对象的所有权
我们程序代码中你可能看到了
就是这样一种情况
这种情况当然是不恰当的
那么如果碰到这种情况
就有可能是出问题的
就是说 如果你在一个指针上边
销毁了那个目标数据对象
并且你还记得我前面的话
在free(p)之后把p设为了NULL
让p不再指向那个目标数据对象
但是你没有改q
两个指针p、q都指向它啊
你把p设成了NULL
在p上面销毁那个目标数据对象
然后把p设成了NULL
可是你没有把q也设成NULL
碰到这种情况 你再一旦尝试使用q
去访问它的目标数据对象的时候
程序就崩掉了 q这个指针就空悬了
因为它指向了一个地方
但那个地方已经没有意义了
已经被销毁了
所以这个指针就空悬了 这叫空悬指针
这是非常有害的一个事情
我们的程序就会因此隐含着bug
我们怎么解决这个问题呢
编程的时候
正常的情况下你要确保
只有一个指针来拥有这个目标数据对象
而其它的指针都只有使用权
而没有所有权
你写程序的时候要有一个清晰的认识
并且最好能够通过名称
来标明什么样的指针有所有权
什么样的指针只有使用权
这样的话 保证你写程序中不会犯错
还要确保 在一个函数里边
最好只有一个指针拥有这个目标数据对象
其他指针只能访问 但是不可以管理
如果有可能
尽可能早地销毁这个目标数据区域
就是说最好的方案就是
在哪一个函数内部分配的这段动态内存
那么就在哪个函数内部销毁它
如果做不到这一点
那么就应该把这一段分配出来的内存
把它的所有权转移给
我们的主调函数中对应的指针
如果在这个函数的主调函数里边
仍然不能够销毁它
那么这个所有权还必须进一步地
向主调函数的主调函数转移
一直转移到main函数
如果main函数还不销毁它
main函数结束的时候
操作系统会全部销毁它了
这个所有权移交的过程
需要由内部最底层的函数
一层一层地向它的主调函数上去移交的
所以我用8个字来总结
这叫“级级上报、层层审批”
我们这个指针所有权
就必须按照这个方式
不断地一级一级地向主调函数上面移交
如果你不销毁 就应该做到这一条
做不到这一条
那么这个指针就会出问题
很重要的一个地方 就是内存泄露
你弄了一个函数
分配了一个动态内存区域
然后函数结束了 那个指针死了
动态内存分配区域呢你忘了销毁
没有任何一个指针指向它了
它那个区域又没有名字
那么它就会变成一个无主之物
无主之物的结果就什么呢
就出了一个垃圾
对于我们内存来讲 就变成了内存泄露
那片内存区域
操作系统已经分配给你了
你的程序本来可以用的
但是没有任何一个机制可以访问它了
指针没了嘛
所以那个地方明明给了你
但是你不能用
就相当于内存那个地方
被挖了一个洞一样
你比如void f()这样一个函数里面
你有一个int *p 然后初始化成new int
最后*p赋值为10
然后函数没了 函数结束了
你new这个int呢
你没有把它的所有权移交
所以new出来的这个int区间没了
它还在 但是没指针访问它了
它就内存泄露了
动态分配内存就应该动态释放
我们的整个程序并不负责管理它的
所以要特别特别注意这一条
所以有的高级语言 比如Java
就提供了一个垃圾回收机制
就说哎呀 干脆让程序员省事
你就只管分配 你别管了
你生成一些垃圾
然后我弄一个垃圾回收机制
搞一个清洁工过来
然后专门替你收拾垃圾
你就不用管了这个垃圾该怎么处理
就叫垃圾回收机制
垃圾回收机制有个巨大问题就什么呢
你不需要它 它效率是最高的
你越需要它 它效率越差
所以Java里面是有的
C/C++里面是没有的
C语言没有 C++里面还是没有
你可以自己写
但是我怀疑那个东西
效率真地不一定会高
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口