- 新增现代 C++ 教程的 Preface 章节,包括英文和中文版本 - 添加 C++ Primer 练习代码 - 新增 Learn C++ 教程的 C++ 开发简介章节 - 添加头文件解析文档 - 更新 mkdocs.yml,包含新教程的目录结构 - 修改项目设置,使用 Python 3.10环境
177 lines
9.4 KiB
Markdown
177 lines
9.4 KiB
Markdown
# 第十二章 动态内存
|
||
|
||
- 对象的生命周期:
|
||
- 全局对象在程序启动时分配,结束时销毁。
|
||
- 局部对象在进入程序块时创建,离开块时销毁。
|
||
- 局部`static`对象在第一次使用前分配,在程序结束时销毁。
|
||
- 动态分配对象:只能显式地被释放。
|
||
|
||
- 对象的内存位置:
|
||
- **静态内存**用来保存局部`static`对象、类`static`对象、定义在任何函数之外的变量。
|
||
- **栈内存**用来保存定义在函数内的非`static`对象。
|
||
- **堆内存**,又称自由空间,用来存储**动态分配**的对象。
|
||
|
||
## 动态内存与智能指针
|
||
|
||
- 动态内存管理:
|
||
- `new`:在动态内存中为对象分配空间并返回一个指向该对象的指针。
|
||
- `delete`:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
|
||
- 智能指针:
|
||
- 管理动态对象。
|
||
- 行为类似常规指针。
|
||
- 负责自动释放所指向的对象。
|
||
- 智能指针也是模板。
|
||
|
||
### shared_ptr类
|
||
|
||
**shared_ptr和unique_ptr都支持的操作**:
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `shared_ptr<T> sp` `unique_ptr<T> up` | 空智能指针,可以指向类型是`T`的对象 |
|
||
| `p` | 将`p`用作一个条件判断,若`p`指向一个对象,则为`true` |
|
||
| `*p` | 解引用`p`,获得它指向的对象。 |
|
||
| `p->mem` | 等价于`(*p).mem` |
|
||
| `p.get()` | 返回`p`中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。 |
|
||
| `swap(p, q)` `p.swap(q)` | 交换`p`和`q`中的指针 |
|
||
|
||
**shared_ptr独有的操作**:
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `make_shared<T>(args)` | 返回一个`shared_ptr`,指向一个动态分配的类型为`T`的对象。使用`args`初始化此对象。 |
|
||
| `shared_ptr<T>p(q)` | `p`是`shared_ptr q`的拷贝;此操作会**递增**`q`中的计数器。`q`中的指针必须能转换为`T*` |
|
||
| `p = q` | `p`和`q`都是`shared_ptr`,所保存的指针必须能互相转换。此操作会**递减**`p`的引用计数,**递增**`q`的引用计数;若`p`的引用计数变为0,则将其管理的原内存释放。 |
|
||
| `p.unique()` | 若`p.use_count()`是1,返回`true`;否则返回`false` |
|
||
| `p.use_count()` | 返回与`p`共享对象的智能指针数量;可能很慢,主要用于调试。 |
|
||
|
||
- **使用动态内存的三种原因**:
|
||
- 程序不知道自己需要使用多少对象(比如容器类)。
|
||
- 程序不知道所需要对象的准确类型。
|
||
- 程序需要在多个对象间共享数据。
|
||
|
||
### 直接管理内存
|
||
|
||
- 用`new`动态分配和初始化对象。
|
||
- `new`无法为分配的对象命名(因为自由空间分配的内存是无名的),因此是返回一个指向该对象的指针。
|
||
- `int *pi = new int(123);`
|
||
- 一旦内存耗尽,会抛出类型是`bad_alloc`的异常。
|
||
- 用`delete`将动态内存归还给系统。
|
||
- 接受一个指针,指向要释放的对象。
|
||
- `delete`后的指针称为空悬指针(dangling pointer)。
|
||
- 使用`new`和`delete`管理动态内存存在三个常见问题:
|
||
- 1.忘记`delete`内存。
|
||
- 2.使用已经释放掉的对象。
|
||
- 3.同一块内存释放两次。
|
||
- 坚持只使用智能指针可以避免上述所有问题。
|
||
|
||
### shared_ptr和new结合使用
|
||
|
||
**定义和改变shared_ptr的其他方法**:
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `shared_ptr<T> p(q)` | `p`管理内置指针`q`所指向的对象;`q`必须指向`new`分配的内存,且能够转换为`T*`类型 |
|
||
| `shared_ptr<T> p(u)` | `p`从`unique_ptr u`那里接管了对象的所有权;将`u`置为空 |
|
||
| `shared_ptr<T> p(q, d)` | `p`接管了内置指针`q`所指向的对象的所有权。`q`必须能转换为`T*`类型。`p`将使用可调用对象`d`来代替`delete`。 |
|
||
| `shared_ptr<T> p(p2, d)` | `p`是`shared_ptr p2`的拷贝,唯一的区别是`p`将可调用对象`d`来代替`delete`。 |
|
||
| `p.reset()` | 若`p`是唯一指向其对象的`shared_ptr`,`reset`会释放此对象。若传递了可选的参数内置指针`q`,会令`p`指向`q`,否则会将`p`置空。若还传递了参数`d`,则会调用`d`而不是`delete`来释放`q`。 |
|
||
| `p.reset(q)` | 同上 |
|
||
| `p.reset(q, d)` | 同上 |
|
||
|
||
### 智能指针和异常
|
||
|
||
- 如果使用智能指针,即使程序块由于异常过早结束,智能指针类也能确保在内存不需要的时候将其释放。
|
||
- **智能指针陷阱**:
|
||
- 不用相同的内置指针初始化(或`reset`)多个智能指针
|
||
- 不`delete get()`返回的指针。
|
||
- 如果你使用`get()`返回的指针,记得当最后一个对应的智能指针销毁后,你的指针就无效了。
|
||
- 如果你使用智能指针管理的资源不是`new`分配的内存,记住传递给它一个删除器。
|
||
|
||
### unique_ptr
|
||
|
||
- 某一个时刻只能有一个`unique_ptr`指向一个给定的对象。
|
||
- 不支持拷贝或者赋值操作。
|
||
- 向后兼容:`auto_ptr`:老版本,具有`unique_ptr`的部分特性。特别是,不能在容器中保存`auto_ptr`,也不能从函数返回`auto_ptr`。
|
||
|
||
**unique_ptr操作**:
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `unique_ptr<T> u1` | 空`unique_ptr`,可以指向类型是`T`的对象。`u1`会使用`delete`来是释放它的指针。 |
|
||
| `unique_ptr<T, D> u2` | `u2`会使用一个类型为`D`的可调用对象来释放它的指针。 |
|
||
| `unique_ptr<T, D> u(d)` | 空`unique_ptr`,指向类型为`T`的对象,用类型为`D`的对象`d`代替`delete` |
|
||
| `u = nullptr` | 释放`u`指向的对象,将`u`置为空。 |
|
||
| `u.release()` | `u`放弃对指针的控制权,返回指针,并将`u`置空。 |
|
||
| `u.reset()` | 释放`u`指向的对象 |
|
||
| `u.reset(q)` | 令`u`指向`q`指向的对象 |
|
||
| `u.reset(nullptr)` | 将`u`置空 |
|
||
|
||
### weak_ptr
|
||
|
||
- `weak_ptr`是一种不控制所指向对象生存期的智能指针。
|
||
- 指向一个由`shared_ptr`管理的对象,不改变`shared_ptr`的引用计数。
|
||
- 一旦最后一个指向对象的`shared_ptr`被销毁,对象就会被释放,不管有没有`weak_ptr`指向该对象。
|
||
|
||
**weak_ptr操作**:
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `weak_ptr<T> w` | 空`weak_ptr`可以指向类型为`T`的对象 |
|
||
| `weak_ptr<T> w(sp)` | 与`shared_ptr`指向相同对象的`weak_ptr`。`T`必须能转换为`sp`指向的类型。 |
|
||
| `w = p` | `p`可以是`shared_ptr`或一个`weak_ptr`。赋值后`w`和`p`共享对象。 |
|
||
| `w.reset()` | 将`w`置为空。 |
|
||
| `w.use_count()` | 与`w`共享对象的`shared_ptr`的数量。 |
|
||
| `w.expired()` | 若`w.use_count()`为0,返回`true`,否则返回`false` |
|
||
| `w.lock()` | 如果`expired`为`true`,则返回一个空`shared_ptr`;否则返回一个指向`w`的对象的`shared_ptr`。 |
|
||
|
||
## 动态数组
|
||
|
||
### new和数组
|
||
|
||
- `new`一个动态数组:
|
||
- 类型名之后加一对方括号,指明分配的对象数目(必须是整型,不必是常量)。
|
||
- 返回**指向第一个对象的指针**。
|
||
- `int *p = new int[size];`
|
||
|
||
- `delete`一个动态数组:
|
||
- `delete [] p;`
|
||
|
||
- `unique_ptr`和数组:
|
||
- 指向数组的`unique_ptr`不支持成员访问运算符(点和箭头)。
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `unique_ptr<T[]> u` | `u`可以指向一个动态分配的数组,整数元素类型为`T` |
|
||
| `unique_ptr<T[]> u(p)` | `u`指向内置指针`p`所指向的动态分配的数组。`p`必须能转换为类型`T*`。 |
|
||
| `u[i]` | 返回`u`拥有的数组中位置`i`处的对象。`u`必须指向一个数组。 |
|
||
|
||
### allocator类
|
||
|
||
- 标准库`allocator`类定义在头文件`memory`中,帮助我们将内存分配和对象构造分离开。
|
||
- 分配的是原始的、未构造的内存。
|
||
- `allocator`是一个模板。
|
||
- `allocator<string> alloc;`
|
||
|
||
**标准库allocator类及其算法**:
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `allocator<T> a` | 定义了一个名为`a`的`allocator`对象,它可以为类型为`T`的对象分配内存 |
|
||
| `a.allocate(n)` | 分配一段原始的、未构造的内存,保存`n`个类型为`T`的对象。 |
|
||
| `a.deallocate(p, n)` | 释放从`T*`指针`p`中地址开始的内存,这块内存保存了`n`个类型为`T`的对象;`p`必须是一个先前由`allocate`返回的指针。且`n`必须是`p`创建时所要求的大小。在调用`deallocate`之前,用户必须对每个在这块内存中创建的对象调用`destroy`。 |
|
||
| `a.construct(p, args)` | `p`必须是一个类型是`T*`的指针,指向一块原始内存;`args`被传递给类型为`T`的构造函数,用来在`p`指向的内存中构造一个对象。 |
|
||
| `a.destroy(p)` | `p`为`T*`类型的指针,此算法对`p`指向的对象执行析构函数。 |
|
||
|
||
**allocator伴随算法**:
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `uninitialized_copy(b, e, b2)` | 从迭代器`b`和`e`给定的输入范围中拷贝元素到迭代器`b2`指定的未构造的原始内存中。`b2`指向的内存必须足够大,能够容纳输入序列中元素的拷贝。 |
|
||
| `uninitialized_copy_n(b, n, b2)` | 从迭代器`b`指向的元素开始,拷贝`n`个元素到`b2`开始的内存中。 |
|
||
| `uninitialized_fill(b, e, t)` | 在迭代器`b`和`e`执行的原始内存范围中创建对象,对象的值均为`t`的拷贝。 |
|
||
| `uninitialized_fill_n(b, n, t)` | 从迭代器`b`指向的内存地址开始创建`n`个对象。`b`必须指向足够大的未构造的原始内存,能够容纳给定数量的对象。 |
|
||
|
||
- 定义在头文件`memory`中。
|
||
- 在给定目的位置创建元素,而不是由系统分配内存给他们。
|