【C++】 Vector容器操作全解析
C++ Vector 容器详解
1. 容量与大小管理
1.1 基本容量操作
size()
:返回当前元素个数capacity()
:返回当前分配的内存容量(可容纳的元素数)empty()
:判断容器是否为空
1.2 容量调整操作
resize(n)
:改变 size若 n > size,新增元素默认初始化(对于基本类型为0,类类型调用默认构造函数)
若 n < size,删除尾部多余元素
可选第二参数指定初始化值:
resize(n, value)
reserve(n)
:改变 capacity只开空间,不初始化,不影响 size
如果 n < capacity,通常无效果(实现可能忽略)
如果 n > capacity,重新分配内存,可能导致迭代器失效
2. Vector 的扩容机制
2.1 不同编译器的增长策略
VS (PJ STL):1.5 倍增长
g++ (SGI STL):2 倍增长
Clang (通常):2 倍增长
2.2 扩容性能分析
摊销常数时间:虽然单次扩容可能耗时,但多次操作的均摊时间复杂度为 O(1)
扩容代价:需要分配新内存、拷贝元素、释放旧内存
reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题。
resize 在开空间的同时还会进行初始化,影响 size。
// 测试vector的默认扩容机制
void TestVectorExpand()
{ size_t sz; vector<int> v; sz = v.capacity(); cout << "making v grow:\n"; for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } }
}
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{ vector<int> v; size_t sz = v.capacity(); v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容 cout << "making bar grow:\n"; for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } }
}
1.2.4vector 增删查改
push_back(val)
:尾部插入(拷贝或移动)
pop_back()
:尾部删除(不返回被删除元素)
emplace_back(args...)
(C++11):直接在尾部构造元素,避免拷贝
emplace(pos, args...)
(C++11):在指定位置直接构造元素
find 查找(注意这个是算法模块实现,不是 vector 的成员接口)
insert 在 position 之前插入 val
erase 删除 position 位置的数据
swap 交换两个 vector 的数据空间
operator[] (重点) 像数组一样访问
4.2 插入操作详解
vector<int> v = {1, 3, 4};// 插入单个元素
v.insert(v.begin() + 1, 2); // v = {1, 2, 3, 4}// 插入多个相同元素
v.insert(v.end(), 3, 5); // v = {1, 2, 3, 4, 5, 5, 5}// 插入范围
vector<int> other = {6, 7, 8};
v.insert(v.end(), other.begin(), other.end()); // v = {1, 2, 3, 4, 5, 5, 5, 6, 7, 8}// 使用初始化列表插入(C++11)
v.insert(v.begin(), {0, -1}); // v = {-1, 0, 1, 2, 3, 4, 5, 5, 5, 6, 7, 8}
4.3 删除操作详解
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9};// 删除单个元素
v.erase(v.begin()); // 删除第一个元素// 删除范围
v.erase(v.begin() + 2, v.begin() + 4); // 删除第3和第4个元素// 删除所有元素
v.clear();// 删除满足条件的元素(使用erase-remove惯用法)
vector<int> v2 = {1, 2, 3, 4, 5, 6, 7, 8, 9};
v2.erase(remove_if(v2.begin(), v2.end(), [](int x) { return x % 2 == 0; }), v2.end());
// 删除所有偶数
4.4 查找操作扩展
#include <algorithm>
#include <vector>vector<int> v = {1, 2, 3, 4, 5, 3, 2, 1};// 查找第一个匹配元素
auto it = find(v.begin(), v.end(), 3);
if (it != v.end()) {cout << "Found at position: " << distance(v.begin(), it) << endl;
}// 查找最后一个匹配元素
auto rit = find(v.rbegin(), v.rend(), 3);
if (rit != v.rend()) {cout << "Last found at position: " << distance(v.begin(), rit.base()) - 1 << endl;
}// 查找第一个满足条件的元素
auto it2 = find_if(v.begin(), v.end(), [](int x) { return x > 3; });// 查找子序列
vector<int> sub = {3, 4};
auto it3 = search(v.begin(), v.end(), sub.begin(), sub.end());
Lambda 表达式
[capture](parameters) -> return_type { function_body }
具体分解
[]
: 捕获列表(空,表示不捕获任何外部变量)(int x)
: 参数列表(接受一个整数参数 x){ return x > 3; }
: 函数体(返回 x > 3 的比较结果)
Lambda 表达式的特性
匿名函数:没有函数名,直接定义和使用
类型推导:编译器自动推导返回类型(这里返回
bool
)捕获能力:可以通过捕获列表访问外部变量(这里没有使用)
等价函数
这个 lambda 表达式等价于以下传统函数:
bool greater_than_three(int x) {return x > 3;
}
Lambda 表达式的更多用法
捕获外部变量
int threshold = 3;
// 通过值捕获外部变量
auto it = find_if(v.begin(), v.end(), [threshold](int x) { return x > threshold; });// 通过引用捕获外部变量
auto it = find_if(v.begin(), v.end(), [&threshold](int x) { return x > threshold; });
指定返回类型
// 显式指定返回类型
auto it = find_if(v.begin(), v.end(), [](int x) -> bool { return x > 3; });
复杂逻辑
// 多个条件的lambda
auto it = find_if(v.begin(), v.end(), [](int x) { return x > 3 && x % 2 == 0; // 大于3的偶数
});
4.5 访问操作扩展
vector<int> v = {1, 2, 3, 4, 5};// 使用operator[](不检查边界)
int a = v[2]; // a = 3// 使用at()(检查边界)
try {int b = v.at(10); // 抛出std::out_of_range异常
} catch (const out_of_range& e) {cout << "Out of range: " << e.what() << endl;
}// 访问首尾元素
int first = v.front(); // 等同于v[0]
int last = v.back(); // 等同于v[v.size()-1]// 直接访问底层数据
int* data = v.data(); // 获取指向底层数组的指针
标准库算法 std::find
的返回值是迭代器:
C++ 标准库的算法(如 find
、sort
等)是通用的,可以适配各种容器(vector
、list
、set
等)。
迭代器为不同容器提供了统一的访问接口:无论容器内部存储结构如何(数组、链表等),算法都能通过迭代器遍历和操作元素。
若不使用迭代器,
find
函数需要为每种容器单独实现(如vector
用索引,list
用指针等),违背了代码复用的原则。