C++ Vector使用指南:常用方法与技巧
C++ Vector 使用指南:常用方法与技巧
std::vector
是 C++ 标准模板库 (STL) 中最常用的序列容器之一。它提供了一种动态数组的功能,可以根据需要自动调整大小,同时保持高效的元素访问和管理。本文将深入探讨 std::vector
的各种用法、常用方法、技巧以及一些高级应用,帮助您充分掌握这个强大的工具。
1. 什么是 Vector?
std::vector
是一个封装了动态大小数组的模板类。与普通数组相比,vector
的优势在于:
- 动态大小:
vector
可以根据元素的添加和删除自动调整其大小,无需手动管理内存。 - 内存连续:
vector
中的元素在内存中是连续存储的,这使得访问元素非常高效(通过索引随机访问)并且与 C 风格数组兼容。 - 丰富的接口:
vector
提供了大量方便的方法来操作元素,如插入、删除、查找等。 - 自动内存管理:
vector
使用 RAII(Resource Acquisition Is Initialization)原则,在对象生命周期结束时自动释放其占用的内存,防止内存泄漏。
2. Vector 的基本使用
2.1 头文件
要使用 std::vector
,需要包含 <vector>
头文件:
```c++
include
```
2.2 声明和初始化
以下是几种声明和初始化 vector
的方法:
```c++
// 1. 默认构造函数,创建一个空的 vector
std::vector
// 2. 创建一个包含 n 个元素的 vector,每个元素都初始化为默认值(对于 int 是 0)
std::vector
// 3. 创建一个包含 n 个元素的 vector,每个元素都初始化为指定的值
std::vector
// 4. 使用初始化列表(C++11 及以后)
std::vector
std::vector
// 5. 使用另一个 vector 复制构造
std::vector
// 6. 使用另一个 vector 的范围构造
std::vector
// 7. 创建一个二维vector
std::vector
```
2.3 访问元素
vector
提供了多种访问元素的方式:
- 下标运算符
[]
: 像数组一样,通过索引访问元素。注意:[]
不进行边界检查,如果索引越界,会导致未定义行为。 at()
方法: 通过索引访问元素,并进行边界检查。如果索引越界,会抛出std::out_of_range
异常。front()
方法: 返回vector
中第一个元素的引用。back()
方法: 返回vector
中最后一个元素的引用。- 迭代器: 使用迭代器遍历
vector
中的元素(稍后详细介绍)。
```c++
std::vector
// 使用 [] 访问
std::cout << vec[0] << std::endl; // 输出 1
// 使用 at() 访问
std::cout << vec.at(2) << std::endl; // 输出 3
// 使用 front() 和 back()
std::cout << vec.front() << std::endl; // 输出 1
std::cout << vec.back() << std::endl; // 输出 5
// 尝试越界访问
// std::cout << vec[10] << std::endl; // 未定义行为
// std::cout << vec.at(10) << std::endl; // 抛出 std::out_of_range 异常
```
2.4 修改元素
```c++
std::vector
// 使用 [] 修改
vec[0] = 10;
// 使用 at() 修改
vec.at(1) = 20;
// vec 现在是 {10, 20, 3}
```
3. Vector 的常用方法
std::vector
提供了丰富的成员函数来操作其内容。
3.1 容量相关
size()
: 返回vector
中当前元素的个数。empty()
: 检查vector
是否为空(即size()
是否为 0)。capacity()
: 返回vector
当前分配的存储空间可以容纳的元素个数。capacity()
通常大于等于size()
。reserve(n)
: 预留至少能容纳n
个元素的存储空间。这可以减少vector
在插入元素时重新分配内存的次数,提高效率。注意:reserve()
不会改变size()
。shrink_to_fit()
: (C++11)请求vector
减少其capacity()
以匹配size()
。这可以释放多余的内存,但并不保证一定会缩小容量(取决于具体实现)。
```c++
std::vector
std::cout << "Size: " << vec.size() << std::endl; // 输出 0
std::cout << "Capacity: " << vec.capacity() << std::endl; // 输出可能是 0 或一个较小的值
vec.reserve(10);
std::cout << "Size: " << vec.size() << std::endl; // 输出 0
std::cout << "Capacity: " << vec.capacity() << std::endl; // 输出 10 或更大
vec.push_back(1);
vec.push_back(2);
std::cout << "Size: " << vec.size() << std::endl; // 输出 2
std::cout << "Capacity: " << vec.capacity() << std::endl; // 输出 10 或更大
vec.shrink_to_fit(); // 请求缩小容量
std::cout << "Size: " << vec.size() << std::endl; // 输出 2
std::cout << "Capacity: " << vec.capacity() << std::endl; // 可能会缩小,但不一定
```
3.2 元素添加和删除
push_back(value)
: 在vector
的末尾添加一个值为value
的元素。pop_back()
: 删除vector
的最后一个元素。insert(position, value)
: 在指定位置position
(迭代器)之前插入一个值为value
的元素。insert(position, n, value)
: 在指定位置插入n个值为value的元素。insert(position, first, last)
: 在指定位置插入范围[first, last)
内的元素。erase(position)
: 删除指定位置position
(迭代器)的元素。erase(first, last)
: 删除范围[first, last)
内的元素。clear()
: 删除vector
中的所有元素,使其大小变为 0,但不影响capacity。emplace_back(args...)
: (C++11) 在vector尾部直接构造一个元素,避免了临时对象的创建和拷贝。emplace(position, args...)
: (C++11) 在指定位置直接构造一个元素。
```c++
std::vector
vec.push_back(4); // vec 现在是 {1, 2, 3, 4}
vec.pop_back(); // vec 现在是 {1, 2, 3}
vec.insert(vec.begin() + 1, 5); // vec 现在是 {1, 5, 2, 3}
vec.insert(vec.begin()+2, 2, 9); // vec 现在是{1,5,9,9,2,3}
std::vector
vec.insert(vec.end(), vec2.begin(), vec2.end()); //vec 现在是{1,5,9,9,2,3,7,8}
vec.erase(vec.begin() + 2); // vec 现在是 {1, 5, 9, 2, 3, 7, 8}
vec.erase(vec.begin() + 1, vec.begin() + 3); //vec 现在是{1,2,3,7,8}
vec.clear(); // vec 现在是 {}
// emplace_back 示例
struct MyObject {
int x;
double y;
MyObject(int x, double y) : x(x), y(y) {}
};
std::vector
objects.emplace_back(10, 3.14); // 直接在 vector 中构造 MyObject 对象
```
3.3 其他常用方法
resize(n)
: 调整vector
的大小为n
。如果n
小于当前大小,则删除多余的元素;如果n
大于当前大小,则添加默认初始化的元素。resize(n, value)
: 调整vector大小为n, 新添加的元素初始化为value.swap(other_vector)
: 交换两个vector
的内容。这是一个非常高效的操作,通常是常数时间复杂度。assign(n, value)
: 将vector
的内容替换为n
个值为value
的元素。assign(first, last)
: 将vector
的内容替换为范围[first,last)
内的元素。operator=
: 赋值运算符, 将一个vector的内容复制到另一个vector.data()
: (C++11) 返回一个指向vector
底层数组的指针, 可以用于与C风格的API交互, 但要注意不要越界访问或修改vector
的大小.
```c++
std::vector
vec.resize(5); // vec 现在是 {1, 2, 3, 0, 0}
vec.resize(2); // vec 现在是 {1, 2}
vec.resize(4, 9); //vec 现在是 {1,2,9,9}
std::vector
vec.swap(vec2); // vec 现在是 {4, 5, 6}, vec2 现在是 {1, 2}
vec.assign(3, 7); //vec 现在是{7,7,7}
std::vector
vec.assign(vec3.begin()+1, vec3.end()-1); //vec 现在是{2,3}
```
4. 迭代器
迭代器是一种用于遍历容器中元素的对象,类似于指针。vector
提供了以下几种迭代器:
begin()
: 返回指向vector
第一个元素的迭代器。end()
: 返回指向vector
最后一个元素之后位置的迭代器(这是一个“past-the-end”迭代器,不指向任何有效元素)。rbegin()
: 返回指向vector
最后一个元素的反向迭代器。rend()
: 返回指向vector
第一个元素之前位置的反向迭代器。cbegin()
、cend()
、crbegin()
、crend()
: (C++11)返回对应迭代器的const
版本,用于只读访问。
```c++
std::vector
// 使用正向迭代器遍历
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " "; // 输出 1 2 3 4 5
}
std::cout << std::endl;
// 使用反向迭代器遍历
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
std::cout << *it << " "; // 输出 5 4 3 2 1
}
std::cout << std::endl;
// 使用 const 迭代器遍历 (C++11)
for (auto it = vec.cbegin(); it != vec.cend(); ++it) {
std::cout << it << " "; // 输出 1 2 3 4 5
// it = 10; // 错误:不能通过 const 迭代器修改元素
}
std::cout << std::endl;
```
迭代器失效问题:
对vector
进行插入或删除操作(如push_back
, insert
, erase
等)可能会导致迭代器失效。失效的迭代器不能再被使用,否则会导致未定义行为。
一般来说,以下规则可以帮助判断迭代器是否失效:
- 插入操作:
- 如果插入操作导致
vector
重新分配内存(即capacity
发生了变化),则所有迭代器、指针和引用都会失效。 - 如果插入操作没有导致
vector
重新分配内存,则指向插入位置之后元素的迭代器、指针和引用会失效,但指向插入位置之前元素的迭代器仍然有效。
- 如果插入操作导致
- 删除操作:
- 指向被删除元素的迭代器、指针和引用会失效。
- 指向被删除元素之后元素的迭代器、指针和引用也会失效。
为了避免迭代器失效问题,可以采取以下措施:
- 在插入或删除元素后,重新获取迭代器。
- 如果需要保存迭代器,可以使用整数索引代替,因为索引不会失效。
- 对于复杂的迭代器操作,可以考虑使用更稳定的容器,如
std::list
或std::deque
。
5. Vector 的高级应用
5.1 作为函数参数和返回值
vector
可以作为函数的参数和返回值。通常,建议使用引用传递(&
)或 const 引用传递(const &
)来避免不必要的复制开销。
```c++
// 通过引用传递,避免复制
void processVector(std::vector
// ... 对 vec 进行修改 ...
}
// 通过 const 引用传递,只读访问
void printVector(const std::vector
for (int x : vec) {
std::cout << x << " ";
}
std::cout << std::endl;
}
// 返回 vector
std::vector
std::vector
// ... 初始化 vec ...
return vec;
}
```
5.2 Vector 与算法
vector
可以与 STL 中的各种算法(如 std::sort
、std::find
、std::transform
等)结合使用,实现强大的功能。
```c++
include
std::vector
// 排序
std::sort(vec.begin(), vec.end()); // vec 现在是 {1, 2, 4, 5, 8, 9}
// 查找
auto it = std::find(vec.begin(), vec.end(), 8);
if (it != vec.end()) {
std::cout << "Found 8 at index: " << std::distance(vec.begin(), it) << std::endl;
}
// 使用 lambda 表达式进行转换
std::transform(vec.begin(), vec.end(), vec.begin(), { return x * 2; });
// vec 现在是 {2, 4, 8, 10, 16, 18}
```
5.3 Vector 存储自定义类型
vector
不仅可以存储内置类型,还可以存储自定义类型(类或结构体)。但要注意,如果自定义类型包含动态分配的资源(如指针),则需要正确实现拷贝构造函数、赋值运算符和析构函数,以避免浅拷贝和内存泄漏问题。 或者使用智能指针。
```c++
struct Person {
std::string name;
int age;
Person(std::string name, int age) : name(name), age(age) {}
};
std::vector
people.emplace_back("Alice", 30);
people.emplace_back("Bob", 25);
```
6. Vector 的性能考虑
- 预分配内存: 如果事先知道
vector
将要存储的元素数量,使用reserve()
预分配足够的内存可以避免多次内存重新分配,提高效率。 - 避免不必要的复制: 在传递
vector
时,尽量使用引用或 const 引用。在添加元素时,如果可以,使用emplace_back()
或emplace()
代替push_back()
或insert()
来避免创建临时对象。 - 选择合适的插入/删除位置: 在
vector
的末尾插入和删除元素(push_back()
和pop_back()
)非常高效(通常是常数时间复杂度)。但在中间或开头插入和删除元素(insert()
和erase()
)会导致后续元素移动,效率较低(线性时间复杂度)。如果需要频繁在中间插入和删除元素,可以考虑使用其他容器,如std::list
。 - 使用移动语义: (C++11) 如果元素类型支持移动语义(move semantics),
vector
的一些操作(如push_back()
、insert()
、resize()
等)会自动利用移动语义来提高效率。
7. 总结
std::vector
是 C++ 中一个非常强大且常用的容器。通过掌握其基本用法、常用方法、迭代器、高级应用以及性能注意事项,您可以编写出更高效、更健壮的 C++ 代码。vector
的灵活性和高效性使其成为处理动态数组和序列数据的理想选择。
希望这篇详细的指南对您有所帮助! 如果您有任何其他问题,请随时提出。