1. Vector类概述:为什么它是C++开发者的必备工具
作为C++标准模板库(STL)中最基础也最重要的容器,vector完美解决了传统C风格数组的所有痛点。想象一下你正在开发一个学生成绩管理系统,使用传统数组时,你必须提前知道学生数量并固定数组大小。而vector则能动态调整容量,就像一个有弹性的容器,随着学生数量的增减自动调整空间。
vector的核心优势在于它结合了数组的高效访问和链表的动态扩展能力。底层实现上,vector使用连续的内存空间存储元素,这使得它既支持O(1)时间复杂度的随机访问,又能根据需要自动扩容。这种设计让vector成为C++中最通用、最高效的容器之一。
关键理解:vector的连续内存特性意味着它的迭代器实际上是原始指针的封装,这也是为什么vector迭代器支持随机访问(可以像指针一样进行算术运算)。
2. Vector的核心接口详解
2.1 构造与初始化:四种创建vector的方式
在实际开发中,我们通常会遇到不同的初始化场景。vector提供了灵活的构造函数来满足这些需求:
cpp复制// 1. 默认构造 - 创建空vector
vector<int> v1;
// 2. 填充构造 - 创建包含5个值为10的元素
vector<int> v2(5, 10);
// 3. 范围构造 - 从数组初始化
int arr[] = {1,2,3};
vector<int> v3(arr, arr+3);
// 4. 列表初始化 (C++11)
vector<int> v4 = {4,5,6};
性能提示:当你知道元素的大致数量时,使用reserve()预分配空间可以避免频繁扩容带来的性能损耗。例如,如果你预计要存储约1000个学生记录,可以先调用v.reserve(1000)。
2.2 迭代器:遍历vector的四种方式
vector提供了丰富的迭代器类型,满足不同遍历需求:
cpp复制vector<int> nums = {1,2,3,4,5};
// 1. 正向迭代器
for(auto it = nums.begin(); it != nums.end(); ++it) {
cout << *it << " ";
}
// 2. 反向迭代器
for(auto rit = nums.rbegin(); rit != nums.rend(); ++rit) {
cout << *rit << " ";
}
// 3. 基于范围的for循环 (C++11)
for(int num : nums) {
cout << num << " ";
}
// 4. 下标访问
for(size_t i = 0; i < nums.size(); ++i) {
cout << nums[i] << " ";
}
注意事项:在循环中修改vector(如添加/删除元素)会导致迭代器失效。这种情况下,要么使用索引访问,要么在修改后重新获取迭代器。
2.3 容量管理:size、capacity和resize
理解size()和capacity()的区别对高效使用vector至关重要:
cpp复制vector<int> v;
v.reserve(100); // 预分配100个元素的空间
cout << "size: " << v.size() << endl; // 输出0
cout << "capacity: " << v.capacity() << endl; // 输出100
v.resize(50); // 将size调整为50,新增元素初始化为0
cout << "size: " << v.size() << endl; // 输出50
cout << "capacity: " << v.capacity() << endl; // 输出100
扩容机制:不同编译器的扩容策略不同,VS通常按1.5倍增长,而g++按2倍增长。了解这一点对性能敏感的应用很重要。
3. Vector的高级用法与性能优化
3.1 元素操作:增删改查的最佳实践
vector提供了多种元素操作方法,各有适用场景:
cpp复制vector<string> names;
// 添加元素
names.push_back("Alice"); // 拷贝构造
names.emplace_back("Bob"); // 直接构造,效率更高
// 插入元素
auto it = names.begin() + 1;
names.insert(it, "Charlie"); // 在第二个位置插入
// 删除元素
names.erase(names.begin()); // 删除第一个元素
names.pop_back(); // 删除最后一个元素
// 访问元素
cout << names[0] << endl; // 无边界检查
cout << names.at(0) << endl; // 有边界检查
性能考虑:push_back和pop_back是O(1)操作,而insert和erase在非尾部位置是O(n)操作,因为需要移动元素。在需要频繁中间插入/删除的场景,考虑使用list或deque。
3.2 迭代器失效问题:你必须知道的陷阱
vector的某些操作会导致迭代器失效,这是常见的错误来源:
cpp复制vector<int> v = {1,2,3,4,5};
// 错误示例:在遍历时删除元素
for(auto it = v.begin(); it != v.end(); ++it) {
if(*it == 3) {
v.erase(it); // 错误!erase后it失效
}
}
// 正确写法
for(auto it = v.begin(); it != v.end(); ) {
if(*it == 3) {
it = v.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
失效场景:
- 插入操作导致扩容时,所有迭代器失效
- 删除操作会使被删除元素及其后的迭代器失效
3.3 内存管理技巧:避免不必要的拷贝
vector在扩容时会进行元素拷贝,对于大型对象这会带来性能问题。解决方法:
cpp复制// 使用移动语义减少拷贝
vector<LargeObject> objects;
objects.emplace_back(args...); // 直接构造,避免拷贝
// 交换技巧释放内存
vector<int>().swap(v); // 清空v并释放所有内存
// C++11的shrink_to_fit
v.shrink_to_fit(); // 请求释放未使用的容量
4. Vector的底层实现与自定义扩展
4.1 模拟实现vector的核心框架
理解vector的底层实现有助于更好地使用它。以下是简化版的vector核心实现:
cpp复制template<typename T>
class SimpleVector {
T* data; // 数据指针
size_t size; // 元素数量
size_t capacity; // 总容量
public:
// 构造函数
SimpleVector() : data(nullptr), size(0), capacity(0) {}
// 析构函数
~SimpleVector() { delete[] data; }
// 添加元素
void push_back(const T& value) {
if(size >= capacity) {
reserve(capacity ? 2 * capacity : 1);
}
data[size++] = value;
}
// 扩容
void reserve(size_t new_cap) {
if(new_cap <= capacity) return;
T* new_data = new T[new_cap];
for(size_t i = 0; i < size; ++i) {
new_data[i] = std::move(data[i]);
}
delete[] data;
data = new_data;
capacity = new_cap;
}
// 其他接口...
};
4.2 使用memcpy的陷阱
在自定义vector实现时,直接使用memcpy拷贝元素是危险的:
cpp复制// 危险的做法 - 对于非POD类型会导致问题
memcpy(new_data, data, size * sizeof(T));
正确做法:对于非平凡类型(如含有虚函数或需要特殊拷贝语义的类),应该使用placement new和显式调用拷贝构造函数:
cpp复制for(size_t i = 0; i < size; ++i) {
new(&new_data[i]) T(data[i]); // 拷贝构造
data[i].~T(); // 析构原对象
}
5. Vector在实际开发中的应用案例
5.1 算法题实战:只出现一次的数字
cpp复制int singleNumber(vector<int>& nums) {
int result = 0;
for(int num : nums) {
result ^= num; // 利用异或性质
}
return result;
}
5.2 多维数据结构:实现动态二维数组
cpp复制// 创建5x5的二维vector
vector<vector<int>> matrix(5, vector<int>(5));
// 动态调整大小
matrix.resize(10); // 行数变为10
for(auto& row : matrix) {
row.resize(10); // 每行列数变为10
}
5.3 高效缓存实现
cpp复制template<typename K, typename V>
class LRUCache {
list<pair<K, V>> items;
unordered_map<K, typename list<pair<K,V>>::iterator> keyToItem;
size_t capacity;
public:
LRUCache(size_t cap) : capacity(cap) {}
V get(K key) {
auto it = keyToItem.find(key);
if(it == keyToItem.end()) {
throw runtime_error("Key not found");
}
items.splice(items.begin(), items, it->second);
return it->second->second;
}
void put(K key, V value) {
auto it = keyToItem.find(key);
if(it != keyToItem.end()) {
items.erase(it->second);
keyToItem.erase(it);
}
items.emplace_front(key, value);
keyToItem[key] = items.begin();
if(keyToItem.size() > capacity) {
keyToItem.erase(items.back().first);
items.pop_back();
}
}
};
6. 性能优化与最佳实践
- 预分配空间:使用reserve()提前分配足够空间,避免频繁扩容
- 选择合适的容器:对于频繁中间插入/删除,考虑list或deque
- 使用emplace_back:C++11后优先使用emplace_back而非push_back
- 避免不必要的拷贝:使用移动语义或swap来转移数据
- 迭代器安全:在修改vector时注意迭代器失效问题
- 算法选择:结合STL算法如sort、find等提高开发效率
vector作为C++中最基础也最强大的容器,掌握它的使用技巧和底层原理,是成为高效C++开发者的必经之路。通过合理运用vector的各种特性,可以写出既高效又易于维护的代码。