第9章 顺序容器

第九章 顺序容器

9.1 顺序容器概述

1.顺序容器类型及核心特性

容器类型 底层结构 随机访问 插入/删除效率 内存管理 适用场景
vector 动态数组 O(1) 尾部O(1),中间/头部O(n) 连续内存,扩容需拷贝 频繁随机访问、尾部操作(如排序、遍历)
deque 分段连续数组 O(1) 头尾O(1),中间O(n) 分段存储,扩容代价小 双端队列、滑动窗口(需两端操作且偶尔随机访问)
list 双向链表 O(n) 任意位置O(1) 非连续内存,额外指针 频繁任意位置插入/删除(如游戏对象管理)
forward_list 单向链表 O(n) 仅支持单向插入/删除(O(1)) 最小内存开销 内存敏感场景,仅需单向操作
array 固定数组 O(1) 不支持 栈分配,大小固定 元素数量固定的场景(替代内置数组)
string 字符动态数组 O(1) 类似vector,尾部高效 连续内存,优化字符串 字符串处理(支持查找、拼接等特殊操作)

2. 性能折中与选择原则

关键权衡因素

  • 随机访问 vs. 插入/删除效率

  • vector/deque支持快速随机访问,但中间插入代价高。

  • list/forward_list任意位置插入高效,但无法随机访问。

  • 内存开销

  • 链表容器(list/forward_list)因指针开销占用更多内存,缓存不友好。

  • vector/deque内存连续,缓存命中率高,但扩容可能引发数据迁移。

选择建议

  1. 默认选择 vector
  • 除非有特殊需求(如频繁中间插入),优先使用 vector(随机访问快、内存高效)。
  1. 特定场景优化
  • 双端操作deque(如任务队列)。
  • 频繁中间插入listforward_list(如实时数据流处理)。
  • 固定大小array(安全替代内置数组)。
  1. 复合策略
  • 输入阶段用 list插入,完成后拷贝到 vector以优化后续访问。

3. 操作复杂度对比

操作 vector deque list forward_list
随机访问 O(1) O(1) O(n) O(n)
头部插入 O(n) O(1) O(1) O(1)
尾部插入 O(1) O(1) O(1) O(n)
中间插入 O(n) O(n) O(1) O(1)
迭代器稳定性 可能失效 可能失效 稳定 稳定

4. 实际应用技巧

  • 预分配内存vector.reserve()避免频繁扩容。

  • 避免迭代器失效list插入/删除不影响其他迭代器,vector/deque需谨慎。

  • 适配器选择stack/queue默认基于 dequepriority_queue基于 vector


总结

顺序容器的选择需综合访问模式操作频率内存效率vector是通用首选,而 list/deque在特定场景下性能更优。现代C++标准库的实现已高度优化,建议优先使用标准容器而非原始数组

9.2 容器库概述

1. 容器操作的层次性

容器操作分为三个层次,适用于不同范围的容器类型:

  1. 所有容器通用的操作
  • 包括构造函数、赋值操作(=)、大小查询(size()empty())、迭代器操作(begin()end())、比较运算符(==!=)等。
  • 例如:swap()可交换两个同类型容器的内容,时间复杂度通常为O(1)。
  1. 特定容器类别的操作
  • 顺序容器(如vectorlistdeque):支持push_back()insert()emplace()等位置相关操作。
  • 关联容器(如mapset):提供基于键的查找(find()count())和排序操作。
  • 无序容器(如unordered_map):支持哈希查找,平均O(1)时间复杂度。
  1. 仅少数容器的特殊操作
  • 例如:listforward_listsplice()用于链表拼接,priority_queuetop()访问最高优先级元素。

2. 容器的头文件与模板类

  • 每个容器定义在与其同名的头文件中(如#include #include )。
  • 容器均为模板类,需指定元素类型(如vectorlist)。
  • 嵌套容器:支持容器元素为其他容器(如vector<vector>),但旧编译器需在尖括号间加空格(如vector< vector >)。

3. 元素类型的限制

  • 通用限制
  • 元素类型必须可拷贝或移动(如unique_ptr需满足移动语义)。
  • 析构函数不能抛出异常。
  • 特定操作的限制
  • 顺序容器的resize(n)要求元素类型有默认构造函数(若无,需通过resize(n, val)指定初始值)。
  • 关联容器的元素需定义比较运算符(默认operator<)。
  • 无序容器的元素需支持哈希函数和相等比较(operator==)。

4. 容器选择与性能考量

  • 默认选择vector(连续内存、随机访问高效),除非有特殊需求。
  • 特殊场景
  • 频繁中间插入/删除:listforward_list(链表结构)。
  • 双端操作:deque(头尾操作高效)。
  • 快速查找:关联容器(有序)或无序容器(哈希)。

5. 代码示例与注意事项

// 嵌套容器定义
vector<vector> lines; // 每个元素是一个string的vector

// 元素类型无默认构造函数的处理
class MyClass {
public:
MyClass(int x) {} // 无默认构造函数
};
vector vec; // 合法
// vec.resize(10); // 错误!需改为:
vec.resize(10, MyClass(0)); // 提供初始值

注意事项

  • 关联容器的键需满足严格弱序(如自定义类型需重载operator<)。

  • 无序容器的性能依赖哈希函数质量,复杂类型需自定义哈希。

9.2.1 迭代器

1. 迭代器的公共接口与特性

所有标准库迭代器遵循统一的接口规范,但不同类别的迭代器支持的操作存在差异:

  • 基础操作
  • 解引用(*iter):访问元素值,类似指针操作
  • 递增(++iter):移动到下一个元素(前向迭代器及以上均支持)
  • 比较(==/!=):判断迭代器位置是否相同
  • 特殊操作
  • 递减(--iter):仅双向和随机访问迭代器支持(如listset的迭代器)
  • 算术运算(+n/-n):仅随机访问迭代器支持(如vectordequearray
  • 例外forward_list迭代器不支持递减操作,因其设计为单向链表

2. 迭代器范围的定义与性质

迭代器范围由两个迭代器 beginend表示,需满足以下条件:

  • 合法性要求
  • 指向同一容器,且 end可为尾后位置(one past the last element)
  • 通过递增 begin可到达 end(即 end不在 begin之前)
  • 左闭合区间([begin, end))
  • 包含 begin指向的元素,但不包含 end指向的位置
  • 关键性质
  1. begin == end时范围为空
  2. begin != end时至少含一个元素,且 begin指向首元素
  3. 有限次递增 begin可使 begin == end(确保循环终止)

3. 迭代器范围的编程实践

安全遍历模式

// 标准遍历循环(适用于所有容器)
for (auto it = container.begin(); it != container.end(); ++it) {
std::cout << *it << " "; // 安全解引用
}

// C++11 范围for循环(等价于迭代器遍历)
for (const auto& elem : container) {
std::cout << elem << " ";
}
  • 注意事项

  • 避免在遍历中修改容器结构(如插入/删除),可能导致迭代器失效

随机访问迭代器(如vector)支持 it + n,但其他迭代器需用 std::advance(it, n)

反向迭代器范围

// 从末尾向前遍历
for (auto rit = container.rbegin(); rit != container.rend(); ++rit) {
std::cout << *rit << " "; // 注意:++rit实际向前移动 } ``` - 反向迭代器的 `++`操作实际指向容器的前一个元素 ------ #### **4. 迭代器类别与容器适配性** | **迭代器类型** | **支持操作** | **典型容器** | | :------------: | :-------------------------: | :------------------------: | | 前向迭代器 | `++`, `*`, `->` | `forward_list` |
| 双向迭代器 | 前向操作 + `--` | `list`, `set`, `map` |
| 随机访问迭代器 | 双向操作 + `+n`, `-n`, `[]` | `vector`, `deque`, `array` |

- **容器适配器(如`stack`、`queue`)无迭代器**,仅通过特定接口操作元素

------

#### **5. 迭代器失效与安全建议**

- **失效场景**:
- **序列容器**(`vector`/`deque`):插入/删除可能使所有迭代器失效(因内存重分配)
- **链表容器**(`list`/`forward_list`):仅影响被操作位置的迭代器
- **安全实践**:
- 修改容器后重新获取迭代器(如 `it = container.insert(it, value)`)
- 使用算法如 `erase-remove`模式避免手动管理迭代器

### 9.2.2 容器类型成员

标准库容器定义了多种类型别名,这些别名提供了与容器元素类型和容器特性相关的统一接口。主要类型别名包括:

- `size_type`:无符号整数类型,用于表示容器大小(如元素数量)
- `iterator`和`const_iterator`:用于遍历容器元素的迭代器类型
- `reverse_iterator`和`const_reverse_iterator`:反向迭代器类型
- `value_type`:容器中元素的类型
- `reference`和`const_reference`:元素引用类型
- `difference_type`:两个迭代器之间距离的有符号整数类型

### 9.2.3 begin和end成员

#### 1. 返回的迭代器类型不同

- **`begin()`**:
- 返回的迭代器类型取决于容器的常量性
- 对于非常量容器,返回`iterator`(可修改元素)
- 对于常量容器,返回`const_iterator`(不可修改元素)
- **`cbegin()`**:
- 无论容器是否为常量,总是返回`const_iterator`(不可修改元素)
- 这是C++11新引入的特性,专门用于获取常量迭代器

#### 2. 使用场景不同

- **`begin()`**:
- 适用于需要修改容器内容的场景
- 当容器是非常量时,可以使用返回的`iterator`修改元素值
- **`cbegin()`**:
- 适用于只需要读取容器内容而不修改的场景
- 提供额外的安全性,防止意外修改容器内容

#### 3. 与auto关键字结合时的行为

- 使用`auto`与`begin()`时,获得的迭代器类型取决于容器的常量性
- 使用`auto`与`cbegin()`时,总是获得`const_iterator`,无论容器的常量性如何

#### 示例代码

```C++
#include
#include

int main() {
std::vector v = {1, 2, 3};
const std::vector cv = {4, 5, 6};

// 使用begin()
auto it1 = v.begin(); // iterator
*it1 = 10; // 可以修改
auto it2 = cv.begin(); // const_iterator
// *it2 = 20; // 编译错误,不能修改

// 使用cbegin()
auto cit1 = v.cbegin(); // const_iterator
// *cit1 = 30; // 编译错误,不能修改
auto cit2 = cv.cbegin(); // const_iterator
// *cit2 = 40; // 编译错误,不能修改

return 0;
}

4. 设计理念

cbegin()的设计遵循了"最小权限原则" - 只提供必要的访问权限(只读),从而增加代码的安全性和稳定性。当确定不需要修改容器内容时,应该优先使用cbegin()cend()

9.2.4 容器定义和初始化

一、默认构造函数初始化

所有STL容器都提供默认构造函数,用于创建一个空容器(array除外)。

std::vector vec; // 空vector
std::list lst; // 空list
std::map<std::string, int> m; // 空map

对于array容器,默认构造的array是非空的,它包含了与其大小一样多的元素,这些元素都被默认初始化。

std::array<int, 5> arr; // 创建包含5个int的array,元素值未定义

二、拷贝初始化

将一个容器初始化为另一个容器的拷贝有两种方式:

  1. 直接拷贝整个容器:要求两个容器的类型及其元素类型必须匹配
std::vector vec1 = {1, 2, 3};
std::vector vec2(vec1); // 直接拷贝
  1. 使用迭代器范围拷贝(array除外):不要求容器类型相同,元素类型可以不同,只要能进行类型转换
std::list lst = {1, 2, 3, 4, 5};
std::vector vec(lst.begin(), lst.end()); // 使用迭代器范围拷贝

三、列表初始化

C++11引入了列表初始化,可以显式指定容器中每个元素的值。

std::vector vec = {1, 2, 3, 4, 5};
std::list lst = {"hello", "world"};
std::map<int, std::string> m = {{1, "one"}, {2, "two"}};

对于array容器,列表初始化时初始值数目可以小于array大小,剩余元素会进行值初始化。

std::array<int, 5> arr = {1, 2, 3}; // 前三个元素为1,2,3,后两个为0

四、大小和初始值初始化

顺序容器(array除外)还提供接受容器大小和(可选)元素初始值的构造函数。

std::vector vec1(10); // 10个元素,默认初始化为0
std::vector vec2(5, "hi"); // 5个元素,每个都是"hi"

如果元素类型没有默认构造函数,必须指定显式的元素初始值。

五、array的特殊初始化

由于array的大小是类型的一部分,它有特殊的初始化要求:

  1. 必须同时指定元素类型和大小
  2. 不支持普通的容器构造函数
  3. 可以进行拷贝和赋值(与内置数组不同)
std::array<int, 4> arr1 = {1, 2, 3, 4};
std::array<int, 4> arr2 = arr1; // 合法,与内置数组不同

六、性能考虑

对于vector等动态容器,可以使用reserve()预先分配空间以减少动态扩展的开销。

std::vector vec;
vec.reserve(100); // 预留空间,避免多次重新分配

七、不同类型容器的初始化特点

  1. 序列容器(vector, list, deque):
  • 支持大小和初始值初始化
  • 支持迭代器范围初始化
  • vector有capacity()和reserve()方法
  1. 关联容器(set, map等):
  • 不支持大小参数初始化
  • 通常使用列表初始化或迭代器范围初始化
  1. 容器适配器(stack, queue):
  • 通常基于其他容器初始化
  • 例如:std::stack<int, std::vector> st;

9.2.5 赋值和swap

一、赋值运算符(=)

基本特性
  1. 所有容器(包括array)都支持赋值运算符
  2. 操作效果:将左边容器中的全部元素替换为右边容器中元素的拷贝
  3. 类型要求:左右两边容器类型必须完全相同(容器类型和元素类型)
示例代码
vector v1 = {1, 2, 3};
vector v2;
v2 = v1; // 正确,类型匹配

array<int, 3> a1 = {4, 5, 6};
array<int, 3> a2;
a2 = a1; // 正确,array支持赋值(与内置数组不同)

list lst1 = {"a", "b", "c"};
deque deq;
// deq = lst1; // 错误,容器类型不匹配
大小变化
  • 赋值后,左边容器的大小将与右边容器相同
  • 如果原大小不同,左边容器会调整大小

二、assign操作(仅顺序容器,array除外)

基本特性
  1. 灵活性:可以从不同但相容的类型赋值,或从子序列赋值
  2. 操作效果:用参数指定的元素替换容器中所有原有元素
  3. 两种形式
  • 接受迭代器范围
  • 接受数量和一个值
示例代码
list names;
vector oldstyle = {"hello", "world"};

// 使用迭代器范围assign
names.assign(oldstyle.begin(), oldstyle.end()); // 将const char*转换为string

// 使用数量和值assign
vector v;
v.assign(5, 10); // 5个元素,每个都是10
注意事项
  • 传递给assign的迭代器不能指向调用assign的容器自身
  • 会替换所有原有元素,容器大小可能改变

三、swap操作

基本特性
  1. 所有容器都支持swap操作
  2. 操作效果:交换两个相同类型容器的内容
  3. 性能差异
  • 对于非array容器:O(1)时间复杂度,只交换内部数据结构
  • 对于array容器:O(n)时间复杂度,真正交换所有元素
示例代码
vector svec1(10); // 10个空string
vector svec2(24); // 24个空string
svec1.swap(svec2); // 交换后svec1有24个元素,svec2有10个

array<int, 3> a1 = {1, 2, 3};
array<int, 3> a2 = {4, 5, 6};
swap(a1, a2); // 真正交换元素
迭代器有效性
容器类型 迭代器/指针/引用有效性
非array容器 保持有效,但指向的元素现在属于另一个容器
array容器 保持绑定到相同元素,但元素值已交换
string 失效

四、成员与非成员swap

两种版本
  1. 成员函数版本container1.swap(container2)
  2. 非成员函数版本swap(container1, container2)
使用建议
  • 在泛型编程中优先使用非成员版本
  • 两种版本效果相同,但非成员版本更通用

五、综合比较表

操作 支持容器 类型要求 时间复杂度 大小影响 迭代器有效性
赋值(=) 所有 完全相同 O(n) 可能改变 通常失效
assign 顺序容器(array除外) 元素类型可转换 O(n) 可能改变 通常失效
swap 所有 完全相同 array: O(n) 其他: O(1) 交换大小 非array: 保持有效但所属容器改变 array: 保持绑定但值交换 string: 失效

六、最佳实践建议

  1. 赋值操作
  • 需要完全替换容器内容时使用
  • 注意类型严格匹配要求
  1. assign操作
  • 当需要从不同类型容器或子序列初始化时使用
  • 比先clear()再insert()更高效
  1. swap操作
  • 需要高效交换容器内容时优先使用
  • 注意array的性能特点
  • 对于非array容器,可用于快速清空容器(vector().swap(v))
  1. 通用编程
  • 优先使用非成员swap
  • 利用swap实现异常安全编程

9.3 顺序容器的操作

9.3.1 向顺序容器添加元素

一、顺序容器概述

顺序容器是C++标准库中一类数据结构,用于在内存中线性存储元素。它们支持元素的动态添加、删除和访问,同时保持元素的顺序

。主要顺序容器包括:

  • vector:动态数组,支持快速随机访问
  • deque:双端队列,支持头尾高效插入/删除
  • list:双向链表,支持任意位置高效插入/删除
  • forward_list:单向链表,更节省空间
  • string:专门用于字符处理的容器
  • array:固定大小数组
二、向顺序容器添加元素

1. 尾部添加元素

push_backemplace_back是向容器尾部添加元素的主要方法:

vector vec;
vec.push_back(10); // 添加元素10到末尾
vec.emplace_back(20); // 直接构造元素20到末尾[6](@ref)
  • 除array和forward_list外,所有顺序容器(包括string)都支持push_back
  • emplace_back直接在容器内存中构造对象,避免了临时对象的创建和移动操作

2. 头部添加元素

push_frontemplace_front用于在容器头部添加元素:

list lst;
lst.push_front(10); // 在头部插入10
lst.emplace_front(20); // 直接构造20在头部[6](@ref)
  • 仅list、forward_list和deque支持push_front
  • deque保证在容器首尾进行插入和删除元素的操作都只花费常数时间

3. 任意位置插入元素

insertemplace提供了更灵活的插入方式:

vector svec;
svec.insert(svec.begin(), "Hello"); // 在开始位置插入
svec.emplace(svec.begin()+1, "World"); // 在第二个位置构造元素[1](@ref)
  • 所有顺序容器(除array)都支持insert操作
  • forward_list有特殊版本的insert
  • 插入操作可能使迭代器失效,特别是vector和string

4. 插入多个元素

insert还支持插入多个相同元素或一个范围内的元素:

svec.insert(svec.end(), 10, "Anna"); // 插入10个"Anna"
vector v = {"quasi","simba","frollo","scarn"};
svec.insert(svec.begin(), v.end()-2, v.end()); // 插入v的最后两个元素[1](@ref)
三、关键概念与性能考虑
1. 容器元素是拷贝

当对象被插入容器时,放入的是对象值的拷贝而非对象本身。容器中的元素与原始对象无关联,后续修改互不影响。

2. 性能影响

不同容器和不同位置的插入操作性能差异显著:

  • vector/string在尾部之外的插入需要移动元素,可能引发内存重分配
  • deque在首尾插入高效(O(1)),中间插入较慢
  • list在任何位置插入都高效(O(1))
3. emplace与push的区别
特性 push操作 emplace操作
对象构造 先构造临时对象再拷贝/移动 直接在容器内存中构造
效率 较低(有额外拷贝/移动) 较高(避免临时对象)
语法 需显式创建对象 直接传递构造参数

9.3.2 访问元素

一、顺序容器元素访问方法概述

顺序容器提供了多种访问元素的方法,主要包括以下几种:

  1. front():返回容器首元素的引用(所有顺序容器都支持,除forward_list外)
  2. back():返回容器尾元素的引用(除forward_list外的所有顺序容器支持)
  3. operator[]:通过下标访问元素(string、vector、deque和array支持)
  4. at():带边界检查的下标访问(string、vector、deque和array支持)
  5. 迭代器访问:通过begin()/end()等迭代器访问元素(所有容器支持)

二、各访问方法详解与比较

1. front() 和 back() 成员函数
vector v = {1, 2, 3};
int& first = v.front(); // 获取首元素引用
int& last = v.back(); // 获取尾元素引用
first = 10; // 修改首元素值
  • 特点
  • 返回元素的引用,可直接修改元素值
  • 容器为空时行为未定义
  • front()在所有顺序容器中可用(除forward_list)
  • back()在除forward_list外的所有顺序容器中可用
2. 下标运算符 operator[]
vector v = {1, 2, 3};
int elem = v[1]; // 获取第二个元素
v[1] = 20; // 修改第二个元素
  • 特点
  • 仅支持快速随机访问容器(string、vector、deque和array)
  • 不进行边界检查,越界访问是未定义行为
  • 访问效率高,但不安全
3. at() 成员函数
vector v = {1, 2, 3};
try {
int elem = v.at(3); // 越界访问,抛出异常
} catch (const out_of_range& e) {
cerr << "Error: " << e.what() << endl;
}
  • 特点
  • 支持与operator[]相同的容器
  • 进行边界检查,越界时抛出std::out_of_range异常
  • 比operator[]安全,但性能稍低

4. 迭代器访问

vector v = {1, 2, 3};
auto it = v.begin(); // 获取首元素迭代器
int first = *it; // 解引用获取值
*it = 10; // 通过迭代器修改值

// 获取尾元素
auto last_it = v.end();
--last_it; // 必须递减,因为end指向尾后位置
int last = *last_it;
  • 特点
  • 所有容器都支持迭代器访问
  • 更通用的访问方式,可用于算法
  • 需要小心处理end()迭代器

三、访问方法的安全性与性能比较

方法 边界检查 异常安全 性能 适用容器范围
operator[] 不安全 最高 string/vector/deque/array
at() 安全 较高 string/vector/deque/array
front()/back() 不安全 所有顺序容器(部分限制)
迭代器 不安全 所有容器

9.3.3 删除元素

1. 删除元素的基本原则

  • 删除操作不会检查参数的有效性,程序员必须保证要删除的位置是合法的。
  • 删除前最好确认容器非空,否则会引发未定义行为。

2. pop_frontpop_back

  • pop_front:删除第一个元素。
  • pop_back:删除最后一个元素。
  • 限制:
  • vectorstring 没有 pop_front
  • forward_list 没有 pop_back
  • 都返回 void,如果需要被删除的元素值,必须 先保存 它再删除。

3. erase 删除指定位置的元素

  • 单个位置删除
it = c.erase(it); // 删除 it 指向的元素,并返回指向下一个元素的迭代器
  • 范围删除
it = c.erase(first, last); // 删除 [first, last) 范围内的元素
  • 返回值是 指向被删元素之后的迭代器

4. 删除所有元素的两种方法

  1. 调用 clear()
  2. 调用 erase(c.begin(), c.end())

📌 要点记忆

  • pop_* 只能删除首尾,erase 可以删任意位置/范围。
  • 删除后迭代器会失效,必须使用返回的迭代器继续操作。
  • clear() 等价于 erase(begin, end)

9.3.4 特殊的forward_list操作

1. 单向链表的结构特点

  • forward_list 是单向链表,每个节点只知道“下一个节点”是谁(next 指针)。
  • 没有办法直接得到前驱节点,因为它的链接是单向的。
  • 删除一个元素时,要改的是它前驱节点的 next 指针,而不是它自己的。

2. 为什么需要特殊操作

举个例子:
假设链表是:

elem1 -> elem2 -> elem3 -> elem4

要删除 elem3,需要让 elem2->next 指向 elem4
但单向链表没有从 elem3 找到 elem2 的直接方法,所以你必须先拿到 elem2,才能删除它后面的节点。

因此:

  • 删除操作变成了 “删除某节点之后的节点”erase_after
  • 插入操作变成了 “在某节点之后插入”insert_after / emplace_after

3. 特殊迭代器 before_begin

  • 在链表头部插入/删除时,仍然需要“前驱”概念。
  • before_begin 提供了一个指向首元素“之前”的位置(实际上是个虚拟位置)。
  • 这样就能用 insert_after(before_begin(), x) 在链表最前面插入元素。

4. 遍历和删除的模式

删除 odd 数(奇数)的例子:

cpp复制编辑auto prev = flist.before_begin();
auto curr = flist.begin();

while (curr != flist.end()) {
if (*curr % 2) // 是奇数
curr = flist.erase_after(prev); // 删除 curr
else {
prev = curr; // 都向前走
++curr;
}
}

关键点:

  • prev 永远指向 curr 的前驱。
  • 删除时只更新 currprev 不动。
  • 不删除时两个一起前进。

5. 总结核心思想

  • 单向链表无法直接访问前驱节点 → 操作必须基于“某节点之后”。
  • 所以有 insert_aftererase_after 代替 inserterase
  • before_begin 让我们能处理链表头的特殊情况。
  • 遍历删除时要同时维护 prevcurr 两个迭代器。

9.3.6 容器操作可能时迭代器失效

1. 添加元素(insert / push_back / emplace 等)

  • vector / string
  • 重新分配存储空间(capacity 扩张):所有迭代器、指针、引用失效。
  • 未重新分配:插入点之前的迭代器、指针、引用仍有效;插入点之后的失效。
  • deque
  • 首尾位置插入:迭代器失效,指针/引用仍有效。
  • 中间位置插入:所有迭代器、指针、引用失效。
  • list / forward_list
  • 插入不会使迭代器、指针、引用失效(包括首前/尾后迭代器)。

2. 删除元素(erase / pop 等)

  • vector / string
  • 删除点之前的迭代器、指针、引用有效。
  • 删除点及之后的失效(尾后迭代器总是失效)。
  • deque
  • 删除首/尾:大部分仍有效(删除尾会使尾后迭代器失效)。
  • 删除中间:所有迭代器、指针、引用失效。
  • list / forward_list
  • 删除元素只会使指向该元素的迭代器、指针、引用失效,其他位置的不受影响。

3. 循环中修改容器的注意事项

  • 修改后必须更新迭代器
  • erase 返回指向下一个元素的迭代器 → 不要再手动 ++iter
  • insert 返回指向新插入元素的迭代器 → 需 ++ 两次跳过新元素和当前元素。
  • 不要缓存 end() 返回的迭代器
  • 对 vector / string / deque(非首尾操作)修改后,原来的 end() 会失效。
  • 解决:每次循环都重新调用 end()

4. 口诀记忆

“增删之后,更新迭代器;end别缓存,容器随时变。”
vector/string:看位置,看扩容。deque:中间毁灭,首尾安全。list系:基本免疫。

运算表达式

#include
#include
#include
#include // for isdigit()
#include // for runtime_error

// 辅助函数:执行具体的计算
int perform_operation(int b, int a, char op) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/':
if (b == 0) throw std::runtime_error("Division by zero");
return a / b;
}
return 0;
}

// 辅助函数:定义操作符的优先级
int precedence(char op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
return 0; // 对于括号,优先级最低
}

int main() {
std::string expression = "(1 + ((2 + 3) * 4) - 5)";
std::cout << "Processing expression: " << expression << std::endl;

```C++
std::stack values; // 存放操作数的栈
std::stack ops; // 存放操作符和括号的栈

for (int i = 0; i < expression.length(); ++i) {
char token = expression[i];

if (token == ' ') {
continue; // 忽略空格
}

// 如果是数字
if (isdigit(token)) {
int num = 0;
// 处理多位数
while (i < expression.length() && isdigit(expression[i])) { num = num * 10 + (expression[i] - '0'); i++; } i--; // 循环会多加一次i,需要减回来 values.push(num); } // 如果是左括号 else if (token == '(') { ops.push(token); } // 如果是右括号 else if (token == ')') { // 这就是题目描述的核心部分 // 循环计算,直到遇到左括号 while (!ops.empty() && ops.top() != '(') { int val2 = values.top(); values.pop(); int val1 = values.top(); values.pop(); char op = ops.top(); ops.pop(); values.push(perform_operation(val2, val1, op)); } if (!ops.empty()) { ops.pop(); // 弹出左括号 '(' } } // 如果是操作符 else { // 当栈顶操作符优先级更高或相等时,先计算栈顶的 while (!ops.empty() && precedence(ops.top()) >= precedence(token)) {
int val2 = values.top(); values.pop();
int val1 = values.top(); values.pop();
char op = ops.top(); ops.pop();
values.push(perform_operation(val2, val1, op));
}
ops.push(token); // 将当前操作符压栈
}
}

// 处理栈中剩余的操作符
while (!ops.empty()) {
int val2 = values.top(); values.pop();
int val1 = values.top(); values.pop();
char op = ops.top(); ops.pop();
values.push(perform_operation(val2, val1, op));
}

// 最终,values栈中剩下的唯一元素就是结果
if (!values.empty()) {
std::cout << "Final result: " << values.top() << std::endl;
}

return 0;
```

}
posted @ 2025-12-20 20:59  belief73  阅读(1)  评论(0)    收藏  举报