
在C++标准模板库(STL)中,容器是非常重要的工具。容器可以帮助我们管理一组对象,并且提供了各种操作这些对象的方法。顺序容器(Sequential Containers)是 C++ 标准库中的一类容器,它们按照元素插入的顺序来存储元素,并且可以通过元素的位置来访问它们。本文详细介绍 C++ 中的顺序容器,包括 vector、deque、list、forward_list 和 array,并提供相应的代码示例。
顺序容器提供了高效的元素存储和访问方式。不同的顺序容器在性能和使用场景上有所差异,主要体现在插入、删除元素的效率,以及随机访问的能力上。以下是 C++ 标准库中常见的顺序容器:
vectorvector 是最常用的顺序容器,它类似于动态数组。vector 可以自动调整大小以容纳更多的元素,并且支持随机访问。在尾部插入和删除元素的效率很高,但在中间或头部插入和删除元素的效率较低。
dequedeque 是双端队列,它支持在头部和尾部高效地插入和删除元素,同时也支持随机访问。与 vector 不同,deque 在头部插入和删除元素的效率也很高。
listlist 是双向链表,它支持在任意位置高效地插入和删除元素,但不支持随机访问。如果需要频繁地在中间插入或删除元素,list 是一个不错的选择。
forward_listforward_list 是单向链表,它只支持单向遍历,不支持反向遍历。forward_list 在插入和删除元素方面比 list 更高效,但功能相对较少。
arrayarray 是固定大小的数组,它的大小在编译时就已经确定。array 支持随机访问,并且在性能上与普通数组相当,但提供了更多的标准库功能。
下面是一个简单的表格对比这些顺序容器的特点:
容器类型 | 随机访问 | 头部插入 / 删除 | 尾部插入 / 删除 | 中间插入 / 删除 |
|---|---|---|---|---|
vector | 支持 | 低效率 | 高效率 | 低效率 |
deque | 支持 | 高效率 | 高效率 | 低效率 |
list | 不支持 | 高效率 | 高效率 | 高效率 |
forward_list | 不支持 | 高效率 | 不支持 | 高效率 |
array | 支持 | 不支持 | 不支持 | 不支持 |
vector 容器vector 的基本使用vector 位于 <vector> 头文件中,使用时需要包含该头文件。以下是一个简单的 vector 使用示例:
#include <iostream>
#include <vector>
int main() {
// 创建一个空的 vector
std::vector<int> vec;
// 向 vector 中添加元素
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
// 访问 vector 中的元素
for (int i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
}
std::cout << std::endl;
return 0;
}
首先创建了一个空的 vector,然后使用 push_back 方法向 vector 中添加元素,最后使用下标运算符 [] 访问 vector 中的元素。
vector 的常用操作size() 方法获取 vector 中元素的个数。empty() 方法检查 vector 是否为空。[] 访问元素外,还可以使用 at() 方法,at() 方法会进行边界检查,如果越界会抛出异常。insert() 方法在指定位置插入元素。erase() 方法删除指定位置的元素,使用 pop_back() 方法删除最后一个元素。以下是一个包含更多操作的示例:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 获取元素个数
std::cout << "Size: " << vec.size() << std::endl;
// 检查是否为空
std::cout << "Is empty: " << (vec.empty() ? "Yes" : "No") << std::endl;
// 使用 at() 方法访问元素
std::cout << "Element at index 2: " << vec.at(2) << std::endl;
// 在指定位置插入元素
vec.insert(vec.begin() + 2, 10);
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
// 删除指定位置的元素
vec.erase(vec.begin() + 3);
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
// 删除最后一个元素
vec.pop_back();
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
vector 的内存管理vector 会自动管理内存,当元素数量超过当前容量时,vector 会重新分配更大的内存空间,并将原有元素复制到新的内存空间中。可以使用 capacity() 方法获取 vector 当前的容量,使用 reserve() 方法预先分配一定的内存空间,以减少内存重新分配的次数。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec;
std::cout << "Initial capacity: " << vec.capacity() << std::endl;
// 预先分配内存
vec.reserve(10);
std::cout << "Capacity after reserve: " << vec.capacity() << std::endl;
for (int i = 0; i < 10; ++i) {
vec.push_back(i);
}
std::cout << "Capacity after adding elements: " << vec.capacity() << std::endl;
return 0;
}
deque 容器deque 的基本使用deque 位于 <deque> 头文件中,使用时需要包含该头文件。deque 的使用方法与 vector 类似,但它支持在头部和尾部高效地插入和删除元素。
#include <iostream>
#include <deque>
int main() {
std::deque<int> deq;
// 在尾部插入元素
deq.push_back(10);
deq.push_back(20);
// 在头部插入元素
deq.push_front(5);
// 访问元素
for (int num : deq) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
deque 的常用操作deque 提供了与 vector 类似的操作,如 size()、empty()、at() 等,同时还提供了 push_front() 和 pop_front() 方法用于在头部插入和删除元素。
#include <iostream>
#include <deque>
int main() {
std::deque<int> deq = {1, 2, 3, 4, 5};
// 获取元素个数
std::cout << "Size: " << deq.size() << std::endl;
// 检查是否为空
std::cout << "Is empty: " << (deq.empty() ? "Yes" : "No") << std::endl;
// 在头部插入元素
deq.push_front(0);
// 在尾部插入元素
deq.push_back(6);
// 访问元素
for (int num : deq) {
std::cout << num << " ";
}
std::cout << std::endl;
// 删除头部元素
deq.pop_front();
// 删除尾部元素
deq.pop_back();
// 访问元素
for (int num : deq) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
list 容器list 的基本使用list 位于 <list> 头文件中,使用时需要包含该头文件。list 是双向链表,支持在任意位置高效地插入和删除元素。
#include <iostream>
#include <list>
int main() {
std::list<int> lst;
// 在尾部插入元素
lst.push_back(10);
lst.push_back(20);
// 在头部插入元素
lst.push_front(5);
// 遍历 list
for (int num : lst) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
list 的常用操作list 提供了一些特殊的操作,如 insert() 可以在指定位置插入元素,erase() 可以删除指定位置的元素,splice() 可以将一个 list 中的元素转移到另一个 list 中。
#include <iostream>
#include <list>
int main() {
std::list<int> lst1 = {1, 2, 3};
std::list<int> lst2 = {4, 5, 6};
// 在指定位置插入元素
auto it = lst1.begin();
++it;
lst1.insert(it, 10);
// 遍历 lst1
for (int num : lst1) {
std::cout << num << " ";
}
std::cout << std::endl;
// 删除指定位置的元素
it = lst1.begin();
++it;
lst1.erase(it);
// 遍历 lst1
for (int num : lst1) {
std::cout << num << " ";
}
std::cout << std::endl;
// 将 lst2 中的元素转移到 lst1 中
lst1.splice(lst1.end(), lst2);
// 遍历 lst1
for (int num : lst1) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
forward_list 容器forward_list 的基本使用forward_list 位于 <forward_list> 头文件中,使用时需要包含该头文件。forward_list 是单向链表,只支持单向遍历。
#include <iostream>
#include <forward_list>
int main() {
std::forward_list<int> flst;
// 在头部插入元素
flst.push_front(10);
flst.push_front(20);
// 遍历 forward_list
for (int num : flst) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
forward_list 的常用操作forward_list 提供了一些特殊的操作,如 insert_after() 可以在指定位置之后插入元素,erase_after() 可以删除指定位置之后的元素。
#include <iostream>
#include <forward_list>
int main() {
std::forward_list<int> flst = {1, 2, 3};
// 在指定位置之后插入元素
auto it = flst.begin();
flst.insert_after(it, 10);
// 遍历 forward_list
for (int num : flst) {
std::cout << num << " ";
}
std::cout << std::endl;
// 删除指定位置之后的元素
it = flst.begin();
flst.erase_after(it);
// 遍历 forward_list
for (int num : flst) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
array 容器array 的基本使用array 位于 <array> 头文件中,使用时需要包含该头文件。array 是固定大小的数组,需要在定义时指定数组的大小。
#include <iostream>
#include <array>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// 遍历 array
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
array 的常用操作array 提供了与普通数组类似的操作,如 size() 可以获取数组的大小,at() 可以访问指定位置的元素。
#include <iostream>
#include <array>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// 获取数组的大小
std::cout << "Size: " << arr.size() << std::endl;
// 访问指定位置的元素
std::cout << "Element at index 2: " << arr.at(2) << std::endl;
return 0;
}
在选择顺序容器时,需要根据具体的需求来决定使用哪种容器。以下是一些选择的建议:
vector 是一个不错的选择。deque 是合适的。list 或 forward_list 是更好的选择。如果只需要单向遍历,forward_list 可以提供更高的效率。array 是一个不错的选择。// 错误示例:在循环中保存end迭代器
std::vector<int> vec = {1,2,3};
auto end_it = vec.end();
for(auto it = vec.begin(); it != end_it; ++it) {
if(*it == 2) {
vec.erase(it); // 导致end_it失效
break;
}
}
// 正确做法:实时获取end迭代器
for(auto it = vec.begin(); it != vec.end(); ) {
if(*it == 2) {
it = vec.erase(it); // 返回下一个有效迭代器
} else {
++it;
}
}reserve()预分配内存insert(iter, first, last)代替多次单次插入emplace_back()直接在容器内构造对象① 时间复杂度对比
操作容器 | 随机访问 | 头部插入 | 尾部插入 | 中间插入 |
|---|---|---|---|---|
vector | O(1) | O(n) | O(1) | O(n) |
deque | O(1) | O(1) | O(1) | O(n) |
list | O(n) | O(1) | O(1) | O(1) |
forward_list | O(n) | O(1) | O(n) | O(1) |
② 内存管理策略
vector扩容机制:
vector<int> v;
v.reserve(50); // 预分配内存
for(int i=0; i<100; ++i){
v.push_back(i);
std::cout << "Size: " << v.size()
<< " Capacity: " << v.capacity() << std::endl;
}扩容过程图示:
[元素][元素][元素][空] → 插入 → [元素][元素][元素][元素] → 扩容 → [元素][元素][元素][元素][空][空][空][空]Q:为什么vector中间插入比list慢? A:vector采用连续内存存储,中间插入需要移动后续所有元素,时间复杂度O(n);而list通过指针调整即可完成,时间复杂度O(1)
Q:何时应该选择forward_list? A:当只需要单向遍历且内存敏感时,forward_list比list节省约25%内存,但失去双向遍历能力
Q:array与原生数组的区别? A:array提供边界检查(at()方法)、标准容器接口和自动内存管理,比原生数组更安全易用
本文详细介绍了 C++ 中的顺序容器,包括 vector、deque、list、forward_list 和 array。每个容器都有其特点和适用场景,在实际编程中,需要根据具体的需求来选择合适的容器。通过合理使用顺序容器,可以提高程序的性能和可维护性。
using声明在模板编程中有着重要应用,如定义模板类型别名等。