文章目录
C++中的 std::vector 详解
在 C++中,std::vector
是一个非常强大且常用的容器。它提供了动态数组的功能,让我们在处理数据集合时更加方便高效。
一、vector 的介绍及使用
1. vector 的介绍
std::vector
是 C++标准模板库(STL)中的一个序列容器,它实现了动态数组的功能。使用 STL 有三个境界:能用、明理、能扩展。学习vector
也可以按照这个方法进行。
2. vector 的使用
2.1 vector 的定义
vector
的构造方式有多种:
- 无参构造:
vector()
。 - 构造并初始化
n
个val
:vector(size_type n, const value_type& val = value_type())
。 - 拷贝构造:
vector (const vector& x)
。 - 使用迭代器进行初始化构造:
vector (InputIterator first, InputIterator last)
。
以下是构造代码演示:
#include <iostream>
#include <vector>
int main() {
// 无参构造
std::vector<int> v1;
// 构造并初始化 n 个 val
std::vector<int> v2(5, 10);
// 拷贝构造
std://vector<int> v3(v2);
// 使用迭代器进行初始化构造
int arr[] = {1, 2, 3};
std::vector<int> v4(arr, arr + sizeof(arr) / sizeof(int));
return 0;
}
2.2 vector iterator 的使用
begin()
和end()
:分别获取第一个数据位置的iterator/const_iterator
和获取最后一个数据的下一个位置的iterator/const_iterator
。rbegin()
和rend()
:分别获取最后一个数据位置的reverse_iterator
和获取第一个数据前一个位置的reverse_iterator
。
同时,vector
还提供了一些关于容量和空间的接口:
size()
:获取数据个数。capacity()
:获取容量大小。empty()
:判断是否为空。resize()
:改变vector
的size
。reserve()
:改变vector
的capacity
。
以下是代码演示:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v;
std::cout << "Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;
v.reserve(10);
std::cout << "After reserve(10), Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;
for (int i = 0; i < 5; ++i) {
v.push_back(i);
}
std::cout << "After push_back 5 elements, Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;
v.resize(8);
std::cout << "After resize(8), Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;
return 0;
}
2.3 vector 空间增长问题
在不同的编译器下,vector
的扩容机制可能不同。在 VS 下,容量通常是按 1.5 倍增长;在 G++下,通常是按 2 倍增长。但具体的增长倍数可能会根据实际需求定义,不能固化认为都是特定的倍数。
例如:
void TestVectorExpand() {
size_t sz;
std::vector<int> v;
sz = v.capacity();
std::cout << "making v grow:\n";
for (int i = 0; i < 100; ++i) {
v.push_back(i);
if (sz!= v.capacity()) {
sz = v.capacity();
std::cout << "capacity changed: " << sz << '\n';
}
}
}
如果确定知道需要用多少空间,可以提前使用reserve
函数开辟空间,以缓解vector
增容的代价缺陷问题。而resize
函数在开空间的同时还会进行初始化,影响size
。
2.4 vector 增删查改
push_back()
:尾插。pop_back()
:尾删。find
:查找(注意这是算法模块实现,不是vector
的成员接口)。insert
:在position
之前插入val
。erase
:删除position
位置的数据。swap
:交换两个vector
的数据空间。operator[]
:像数组一样访问。
以下是代码演示:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
v.push_back(6);
std::cout << "After push_back: ";
for (auto e : v) {
std::cout << e << " ";
}
std::cout << std::endl;
v.pop_back();
std::cout << "After pop_back: ";
for (auto e : v) {
std::cout << e << " ";
}
std::cout << std::endl;
// 使用 insert 在特定位置插入元素
v.insert(v.begin() + 2, 7);
std::cout << "After insert: ";
for (auto e : v) {
std::cout << e << " ";
}
std::cout << std::endl;
// 使用 erase 删除特定位置元素
v.erase(v.begin() + 3);
std::cout << "After erase: ";
for (auto e : v) {
std::cout << e << " ";
}
std::cout << std::endl;
// 使用 swap 交换两个 vector 的数据空间
std::vector<int> v2{8, 9, 10};
v.swap(v2);
std::cout << "After swap with v2: ";
for (auto e : v) {
std::cout << e << " ";
}
std::cout << std::endl;
// 使用 operator[] 像数组一样访问元素
std::cout << "Element at index 2: " << v[2] << std::endl;
return 0;
}
2.5 vector 迭代器失效问题
对于vector
,可能会导致其迭代器失效的操作有:
- 会引起其底层空间改变的操作,都有可能使迭代器失效,比如:
resize
、reserve
、insert
、assign
、push_back
等。 - 指定位置元素的删除操作
erase
。
例如:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6};
auto it = v.begin();
// 将有效元素个数增加到 100 个,多出的位置使用 8 填充,操作期间底层会扩容
// v.resize(100, 8);
// reserve 的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
// v.reserve(100);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
// v.insert(v.begin(), 0);
// v.push_back(8);
// 给 vector 重新赋值,可能会引起底层容量改变
v.assign(100, 8);
// 以上操作可能导致迭代器失效,继续使用可能会使程序崩溃
// 解决方式:在这些操作完成之后,如果想要继续通过迭代器操作 vector 中的元素,只需给 it 重新赋值即可。
while (it!= v.end()) {
std::cout << *it << " ";
++it;
}
std::cout << std::endl;
return 0;
}
当使用erase
删除vector
中元素时,如果删除的是最后一个元素,可能会导致迭代器失效。在 VS 下,认为该位置迭代器失效了。
以下是删除vector
中所有偶数的正确代码:
int main() {
std::vector<int> v{1, 2, 3, 4};
auto it = v.begin();
while (it!= v.end()) {
if (*it % 2 == 0) {
it = v.erase(it);
} else {
++it;
}
}
return 0;
}
迭代器失效解决办法:在使用前,对迭代器重新赋值即可。
2.6 vector 在 OJ 中的使用
例如:
- 只出现一次的数字 i。
- 杨辉三角 OJ。
class Solution {
public:
int singleNumber(std::vector<int>& nums) {
int value = 0;
for (auto e : nums) {
value ^= e;
}
return value;
}
};
class Solution {
public:
std::vector<std::vector<int>> generate(int numRows) {
std::vector<std::vector<int>> vv(numRows);
for (int i = 0; i < numRows; ++i) {
vv[i].resize(i + 1, 1);
}
for (int i = 2; i < numRows; ++i) {
for (int j = 1; j < i; ++j) {
vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
}
}
return vv;
}
};
二、vector 深度剖析及模拟实现
1. std::vector 的核心框架接口的模拟实现 bit::vector
可以尝试模拟实现vector
的一些核心接口,以加深对其理解。
2. 使用 memcpy 拷贝问题
如果在模拟实现的vector
中的reserve
接口中使用memcpy
进行拷贝,当拷贝的对象中涉及到资源管理时,会出现问题,因为memcpy
是浅拷贝。
例如:
int main() {
bite::vector<bite::string> v;
v.push_back("1111");
v.push_back("2222");
v.push_back("3333");
return 0;
}
结论:如果对象中涉及到资源管理时,千万不能使用memcpy
进行对象之间的拷贝,否则可能会引起内存泄漏甚至程序崩溃。
3. 动态二维数组理解
可以使用标准库中的vector
构建动态二维数组,例如以杨辉三角的前n
行为例:
void test2vector(size_t n) {
bit::vector<bit::vector<int>> vv(n);
for (size_t i = 0; i < n; ++i) {
vv[i].resize(i + 1, 1);
}
for (int i = 2; i < n; ++i) {
for (int j = 1; j < i; ++j) {
vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
}
}
}
总之,std::vector
是 C++中非常重要的容器,掌握其各种特性和使用方法对于高效编程至关重要。通过深入理解其原理并进行模拟实现,可以更好地运用它来解决实际问题。