doc/docs/Cpp-Notes-main/Cpp-Primer/12.动态内存.md
sairate b5160be045 docs(book): 添加现代 C++教程及相关代码
- 新增现代 C++ 教程的 Preface 章节,包括英文和中文版本
- 添加 C++ Primer 练习代码
- 新增 Learn C++ 教程的 C++ 开发简介章节
- 添加头文件解析文档
- 更新 mkdocs.yml,包含新教程的目录结构
- 修改项目设置,使用 Python 3.10环境
2025-07-06 16:14:30 +08:00

177 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 第十二章 动态内存
- 对象的生命周期:
- 全局对象在程序启动时分配,结束时销毁。
- 局部对象在进入程序块时创建,离开块时销毁。
- 局部`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`中。
- 在给定目的位置创建元素,而不是由系统分配内存给他们。