
vector是C++标准模板库(STL)中的一种顺序容器,用于存储动态数组。与普通的C风格数组不同,vector的大小可以在运行时自动调整,不需要在编译时确定大小。vector本质上是一个模板类,可以存储任意类型的对象,只要这些对象满足可复制和可移动的要求。
动态数组:vector 是 C++ 标准库中的序列容器,本质是一个可动态扩展的数组。它在堆内存中分配连续空间,支持 O(1) 时间的随机访问。
核心优势:
vector能够根据需要自动扩展其大小,插入新元素时,如果当前容量不足,会自动分配更大的内存空间,并将原有元素复制到新的内存空间中。
vector使用连续的内存空间存储元素,因此可以通过下标操作符[]高效地访问任意位置的元素,时间复杂度为O(1)。
vector自动管理其内存空间,不需要手动释放内存,避免了内存泄漏的风险。
vector支持多种迭代器,包括正向迭代器、反向迭代器和常量迭代器,方便对元素进行遍历和修改。
与原生数组对比:
int arr[5]; // 固定大小
std::vector<int> v; // 动态大小
v.reserve(5); // 可预分配空间要使用 vector,需要包含 <vector> 头文件:
#include <vector>
using namespace std;
// 定义一个空的vector
vector<int> vec1;
// 定义一个包含5个元素的vector,每个元素初始化为10
vector<int> vec2(5, 10);
// 使用初始化列表定义一个vector
vector<int> vec3 = {1, 2, 3, 4, 5};
// 通过复制构造函数初始化
vector<int> vec4(vec3);vec1.push_back(10); // 在末尾插入元素
vec1.insert(vec1.begin() + 1, 20); // 在指定位置插入元素vec1.pop_back(); // 删除末尾元素
vec1.erase(vec1.begin() + 2); // 删除指定位置的元素int value = vec1[0]; // 访问第一个元素at方法:提供边界检查,若越界会抛出std::out_of_range异常。try {
int value = vec1.at(1); // 访问第二个元素
} catch (const std::out_of_range& e) {
cerr << "Out of range: " << e.what() << endl;
}front和back方法:分别返回第一个和最后一个元素的引用。int first = vec1.front();
int last = vec1.back();迭代器类型:
// 正向迭代器
for(auto it = nums.begin(); it != nums.end(); ++it) {
std::cout << *it << " ";
}
// 反向迭代(C++11)
for(auto rit = nums.rbegin(); rit != nums.rend(); ++rit) {
std::cout << *rit << " ";
}
// 常量迭代器
auto cit = nums.cbegin(); // 不可修改元素结合标准算法:
#include <algorithm>
// 查找元素
auto pos = std::find(nums.begin(), nums.end(), 999);
// 排序
std::sort(nums.begin(), nums.end());
// 遍历处理(C++11 lambda)
std::for_each(nums.begin(), nums.end(), [](int n) {
std::cout << n * 2 << " ";
});size方法:返回当前元素的数量。
size_t numElements = vec1.size();capacity方法:返回当前分配的存储空间能容纳的元素数量。
size_t capacity = vec1.capacity();empty方法:判断vector是否为空。
bool isEmpty = vec1.empty();resize方法:调整vector的大小,若新大小大于原大小,会用默认值填充新增位置;若新大小小于原大小,会删除多余元素。
vec1.resize(10, 0); // 调整大小为10,新增元素初始化为0reserve方法:为vector预留一定的存储空间,避免频繁重新分配内存。
vec1.reserve(100); // 预留100个元素的存储空间shrink_to_fit方法:请求vector减小其容量以匹配其大小,释放多余的内存。
vec1.shrink_to_fit();扩容机制:
size() == capacity() 时触发扩容
优化策略:
// 预分配空间避免多次扩容
std::vector<BigObject> bigVec;
bigVec.reserve(1000); // 预先分配足够空间
// 使用移动语义减少拷贝
std::vector<std::string> strVec;
strVec.emplace_back("hello"); // 直接构造,避免临时对象vector 最常见的用途之一是作为动态数组。例如,可以读取用户输入的一组整数,并将它们存储在 vector 中:
#include <vector>
#include <iostream>
int main() {
std::vector<int> numbers;
int num;
std::cout << "Enter integers (enter -1 to stop):" << std::endl;
while (true) {
std::cin >> num;
if (num == -1) {
break;
}
numbers.push_back(num);
}
// 输出所有元素
for (int i = 0; i < numbers.size(); ++i) {
std::cout << numbers[i] << " ";
}
std::cout << std::endl;
return 0;
}
vector 可以存储自定义类型的对象。例如,定义一个 Person 类,并将多个 Person 对象存储在 vector 中:
#include <vector>
#include <iostream>
#include <string>
class Person {
public:
Person(const std::string& name, int age) : name(name), age(age) {}
void printInfo() const {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
private:
std::string name;
int age;
};
int main() {
std::vector<Person> people;
// 添加 Person 对象
people.emplace_back("Alice", 25);
people.emplace_back("Bob", 30);
// 输出所有 Person 对象的信息
for (const auto& person : people) {
person.printInfo();
}
return 0;
}
使用了 emplace_back() 来直接在 vector 的末尾构造 Person 对象,避免了不必要的拷贝。
template<typename T>
class Stack {
private:
std::vector<T> elements;
public:
void push(const T& val) { elements.push_back(val); }
void pop() { elements.pop_back(); }
T top() const { return elements.back(); }
};std::vector<std::vector<int>> matrix = {
{1,2,3},
{4,5,6},
{7,8,9}
};解释:迭代器是用于访问容器元素的对象,当对 vector 进行插入、删除等操作时,可能会导致迭代器失效,使得后续对迭代器的操作产生未定义行为。
vector 的容量不足,会重新分配内存,将原有元素复制到新的内存区域,此时所有迭代器都会失效。
典型场景:
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin() + 1;
// 插入元素(可能导致扩容)
vec.push_back(4); // 可能使 it 失效!
// 错误:此时使用 it 是未定义行为
*it = 5; // ❌ 危险操作!解决办法:
it = vec.begin() + 1; // 重新计算迭代器位置size_t index = 1;
vec[index] = 5; // 安全操作解释:vector 的容量(capacity())是指它当前可以容纳的元素数量,当元素数量超过容量时,vector 会自动重新分配更大的内存空间,将原有元素复制到新的内存区域,这会带来性能开销。
示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec;
for (int i = 0; i < 100; ++i) {
vec.push_back(i);
// 每次容量不足时都会重新分配内存
std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
}
return 0;
}
解决办法:在插入大量元素之前,可以使用 reserve() 方法预先分配足够的内存,避免多次重新分配内存
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec;
vec.reserve(100); // 预先分配足够的内存
for (int i = 0; i < 100; ++i) {
vec.push_back(i);
std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
}
return 0;
}
解释:使用下标运算符 [] 访问 vector 元素时,不会进行边界检查,如果下标越界,会导致未定义行为。
示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
// 错误:越界访问,未定义行为
std::cout << vec[3] << std::endl;
return 0;
}解决办法:使用 at() 方法访问元素,它会进行边界检查,如果越界会抛出 std::out_of_range 异常
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
try {
std::cout << vec.at(3) << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "Out of range error: " << e.what() << std::endl;
}
return 0;
}解释:当 vector 作为函数参数传递或赋值时,会涉及拷贝或移动操作。如果 vector 中存储的是大对象,拷贝操作会带来性能开销。
示例代码:
#include <iostream>
#include <vector>
#include <string>
void printVector(std::vector<std::string> vec) {
for (const auto& str : vec) {
std::cout << str << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<std::string> vec = {"apple", "banana", "cherry"};
// 拷贝操作,可能带来性能开销
printVector(vec);
return 0;
}
解决办法:
#include <iostream>
#include <vector>
#include <string>
void printVector(const std::vector<std::string>& vec) {
for (const auto& str : vec) {
std::cout << str << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<std::string> vec = {"apple", "banana", "cherry"};
printVector(vec);
return 0;
}std::move() 进行移动操作:#include <iostream>
#include <vector>
#include <string>
std::vector<std::string> getVector() {
std::vector<std::string> vec = {"apple", "banana", "cherry"};
return std::move(vec);
}
int main() {
std::vector<std::string> newVec = getVector();
return 0;
}解释:在 vector 进行插入、删除等操作时,如果发生异常,可能会导致 vector 处于不一致的状态。
示例代码:
#include <iostream>
#include <vector>
class BigObject {
public:
BigObject() { /* 可能抛出异常的构造函数 */ }
};
int main() {
std::vector<BigObject> vec;
try {
vec.push_back(BigObject());
} catch (...) {
// 插入操作可能失败,vec 状态不确定
}
return 0;
}解决办法:使用 emplace_back() 代替 push_back(),emplace_back() 直接在 vector 的末尾构造对象,减少不必要的拷贝和移动,并且在构造过程中如果发生异常,vector 的状态不会改变。
#include <iostream>
#include <vector>
class BigObject {
public:
BigObject() { /* 可能抛出异常的构造函数 */ }
};
int main() {
std::vector<BigObject> vec;
try {
vec.emplace_back();
} catch (...) {
// 插入操作失败,vec 状态不变
}
return 0;
}vector<bool> 的特殊陷阱核心问题:vector<bool> 是空间优化的特化版本,存储方式不同于其他 vector<T>。
典型错误:
std::vector<bool> flags{true, false};
// 尝试获取 bool 引用
bool& flag = flags[0]; // ❌ 编译错误!返回的是临时代理对象
flags[1] = true; // ✔️ 允许通过代理对象赋值替代方案
std::vector<char> bools(10, 0); // 用 char 模拟 boolstd::bitset<100> bits; // 固定大小的位集合常见错误:误用 reserve() 和 resize()。
错误示例:
std::vector<int> vec;
vec.reserve(10); // 分配容量为 10,size 仍为 0
vec[0] = 1; // ❌ 越界访问!size() 未改变
std::cout << vec[0]; // 未定义行为
vec.resize(10); // ✔️ 正确调整大小
vec[9] = 10; // 安全操作关键区别:
方法 | 作用 | 时间复杂度 |
|---|---|---|
reserve(n) | 仅预分配内存(容量) | O(n) |
resize(n) | 修改元素数量(改变大小) | O(n) |
关键问题:存储原始指针导致内存泄漏。
危险代码:
std::vector<MyClass*> ptr_vec;
ptr_vec.push_back(new MyClass());
// 忘记释放内存...
// ptr_vec 析构时不会自动 delete 指针!现代 C++ 解决方案:使用智能指针
std::vector<std::unique_ptr<MyClass>> safe_vec;
safe_vec.push_back(std::make_unique<MyClass>()); // 自动管理内存核心原则:vector 不是线程安全的容器。
危险场景:
std::vector<int> shared_vec;
// 线程1
void write() {
shared_vec.push_back(42);
}
// 线程2
void read() {
if (!shared_vec.empty()) {
int val = shared_vec[0]; // 可能读取到无效数据
}
}解决方案:使用互斥锁
std::mutex mtx;
void safe_write() {
std::lock_guard<std::mutex> lock(mtx);
shared_vec.push_back(42);
}
void safe_read() {
std::lock_guard<std::mutex> lock(mtx);
if (!shared_vec.empty()) {
int val = shared_vec[0];
}
}特性 | vector | deque | list |
|---|---|---|---|
内存布局 | 连续 | 分段连续 | 非连续 |
随机访问 | O(1) | O(1) | O(n) |
头部插入删除 | O(n) | O(1) | O(1) |
迭代器失效频率 | 高 | 中等 | 低 |
缓存友好性 | 优 | 良 | 差 |
选择策略:
reserve() 已知大小的空间
std::move
通过深入理解 vector 的内部机制和正确使用模式,可以充分发挥其高性能特性,构建高效可靠的 C++ 应用程序。
using声明在模板编程中有着重要应用,如定义模板类型别名等。