文章目录
vector的介绍及使用
介绍
在 C++ 中,vector
是标准模板库(STL)提供的动态数组容器,它可以存储同类型元素,并且支持动态大小调整,是日常开发中非常常用的数据结构。
使用
构造函数
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 构造一个空的 vector,不包含任何元素
vector<int> v1;
cout << "v1 的大小: " << v1.size() << endl;
cout << "v1 是否为空: " << (v1.empty() ? "是" : "否") << endl;
return 0;
}
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 构造一个包含 5 个值为 10 的 int 类型 vector
vector<int> v2(5, 10);
cout << "v2 的元素: ";
for (int num : v2) {
cout << num << " ";
}
cout << endl;
return 0;
}
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v3 = {1, 2, 3, 4, 5};
// 使用拷贝构造函数,构造一个与 v3 内容相同的 vector v4
vector<int> v4(v3);
cout << "v4 的元素: ";
for (int num : v4) {
cout << num << " ";
}
cout << endl;
return 0;
}
cpp
复制代码
#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main() {
list<int> l = {6, 7, 8, 9, 10};
// 使用 list 的迭代器范围 [l.begin(), l.end()) 来构造 vector v5
vector<int> v5(l.begin(), l.end());
cout << "v5 的元素: ";
for (int num : v5) {
cout << num << " ";
}
cout << endl;
return 0;
}
iterator的使用
iterator的使用 |
接口说明 |
begin+end |
获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator |
rbegin+rend |
获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置 |
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
// 使用正向迭代器遍历 vector
cout << "正向遍历:";
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
}
cout << endl;
return 0;
}
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
// 使用反向迭代器遍历 vector
cout << "反向遍历:";
for (vector<int>::reverse_iterator rit = v.rbegin(); rit != v.rend(); ++rit) {
cout << *rit << " ";
}
cout << endl;
return 0;
}
vector的空间增长
- capacity的代码在vs和g++下分别运行会发现vs下capacity是按1.5倍增长的,g++是按2倍增长的。所以不同编译器容量增长策略可能不同,实际以编译器为准)
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题
- resize在开空间的同时还会进行初始化,影响size。
函数基础使用
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
// size:获取数据个数
cout << "size: " << v.size() << endl;
// capacity:获取容量大小
cout << "capacity: " << v.capacity() << endl;
// empty:判断是否为空
cout << "empty: " << (v.empty() ? "是" : "否") << endl;
// resize:改变 vector 的 size
v.resize(8, 10); // 扩容到 8 个元素,新增元素值为 10
cout << "resize 后 size: " << v.size() << endl;
cout << "resize 后元素: ";
for (int num : v) {
cout << num << " ";
}
cout << endl;
// reserve:改变 vector 的 capacity
v.reserve(10);
cout << "reserve 后 capacity: " << v.capacity() << endl;
return 0;
}
reserve缓解增容代价
cpp
复制代码
#include <iostream>
#include <vector>
#include <chrono> // 仅用于计时,不涉及其他容器
using namespace std;
using namespace chrono;
int main() {
// 情况1:不使用 reserve,依赖 vector 自动增容
vector<int> v1;
auto start1 = high_resolution_clock::now(); // 记录开始时间
for (int i = 0; i < 1000000; ++i) { // 插入100万个元素
v1.push_back(i);
// 每次容量不足时,vector 会重新分配更大的内存(通常是当前容量的2倍)
// 并将原有元素复制到新内存,这个过程会消耗额外时间
}
auto end1 = high_resolution_clock::now(); // 记录结束时间
cout << "不使用 reserve 耗时: "
<< duration_cast<microseconds>(end1 - start1).count() << " 微秒" << endl;
// 情况2:使用 reserve 预先开辟足够空间
vector<int> v2;
v2.reserve(1000000); // 提前申请能容纳100万个元素的空间
auto start2 = high_resolution_clock::now();
for (int i = 0; i < 1000000; ++i) {
v2.push_back(i);
// 由于提前开辟了足够空间,不会触发重新分配内存,插入效率更高
}
auto end2 = high_resolution_clock::now();
cout << "使用 reserve 耗时: "
<< duration_cast<microseconds>(end2 - start2).count() << " 微秒" << endl;
return 0;
}
reszie 开空间的同时初始化、影响size
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
// resize 开空间到 5,并初始化为 10,size 变为 5
v.resize(5, 10);
cout << "resize 后 size: " << v.size() << endl;
cout << "resize 后元素: ";
for (int num : v) {
cout << num << " ";
}
cout << endl;
return 0;
}
增删查改
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 初始化一个 vector
vector<int> v = {1, 2, 3, 4, 5};
// push_back:尾插
v.push_back(6);
cout << "push_back 后元素:";
for (int num : v) {
cout << num << " ";
}
cout << endl;
// pop_back:尾删
v.pop_back();
cout << "pop_back 后元素:";
for (int num : v) {
cout << num << " ";
}
cout << endl;
// insert:在 position 之前插入 val
// 这里在索引为 2 的位置(即元素 3 之前)插入 10
v.insert(v.begin() + 2, 10);
cout << "insert 后元素:";
for (int num : v) {
cout << num << " ";
}
cout << endl;
// erase:删除 position 位置的数据
// 这里删除索引为 3 的位置的元素
v.erase(v.begin() + 3);
cout << "erase 后元素:";
for (int num : v) {
cout << num << " ";
}
cout << endl;
// swap:交换两个 vector 的数据空间
vector<int> v2 = {100, 200, 300};
v.swap(v2);
cout << "v 交换后元素:";
for (int num : v) {
cout << num << " ";
}
cout << endl;
cout << "v2 交换后元素:";
for (int num : v2) {
cout << num << " ";
}
cout << endl;
// operator[]:像数组一样访问
cout << "v[1] 的值:" << v[1] << endl;
return 0;
}
vector 迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对 指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器 底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即 如果继续使用已经失效的迭代器,程序可能会崩溃)。
插入操作导致迭代器失效
push_back
与 emplace_back
- 当
vector
的容量(capacity
)足够时,在尾部插入元素不会导致迭代器失效,因为元素插入在末尾其他元素的位置不变。
- 但当
vector
容量不足,需要重新分配内存时,所有指向vector
的迭代器都会失效。这是因为重新分配内存后,vector
中元素的存储地址发生了改变。
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
vector<int>::iterator it = v.begin();
v.push_back(4); // 假设此时容量足够,迭代器it仍有效
cout << *it << endl;
v.push_back(5); // 假设此时容量不足,重新分配内存,迭代器it失效
// cout << *it << endl; // 这会导致未定义行为
return 0;
}
insert
:在vector
的任意位置插入元素,如果插入操作导致内存重新分配,所有迭代器都会失效。若没有重新分配内存,插入点之后的迭代器会失效,因为插入元素后,插入点之后的元素位置都发生了改变
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
v.insert(v.begin(), 0); // 插入操作,假设未导致内存重新分配
// cout << *it << endl; // 迭代器it已失效,会导致未定义行为
return 0;
}
删除操作导致的迭代器失效
pop_back
:删除vector
的尾部元素,如果删除后vector
的容量没有改变,除了指向被删除元素的迭代器外,其他迭代器仍然有效。但如果删除后vector
的容量发生变化(比如释放了部分内存),则指向vector
的迭代器可能会失效。
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
v.pop_back(); // 迭代器it仍然有效
cout << *it << endl;
return 0;
}
erase
:删除指定位置的元素,指向被删除元素以及其后的迭代器都会失效,因为被删除元素之后的元素会向前移动。
cpp
复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
v.erase(v.begin()); // 迭代器it已失效
// cout << *it << endl; // 会导致未定义行为
return 0;
}
resize
和 reserve
操作导致的迭代器失效
resize
:调整vector
的大小,如果新大小比原来小,超出新大小部分的迭代器会失效;如果新大小比原来大且需要重新分配内存,所有迭代器都会失效;如果新大小比原来大但不需要重新分配内存,插入新元素位置之后的迭代器会失效。
reserve
:如果reserve
的参数大于当前capacity
,会导致内存重新分配,所有迭代器都会失效;如果reserve
的参数小于等于当前capacity
,迭代器不会失效。
避免迭代器失效的方法
- 捕获插入或删除操作返回的迭代器 :
insert
和erase
操作会返回一个新的迭代器,指向插入或删除操作之后的位置,通过更新迭代器,可以确保迭代器的有效性。
- 在循环中使用迭代器时特别注意 :在对
vector
进行插入或删除操作的循环中,每次操作后及时更新迭代器,以避免迭代器失效带来的问题。
vector的模拟实现
模拟实现
cpp
复制代码
#pragma once
#include <iostream>
#include <assert.h>
#include <cstring>
using namespace std;
namespace myvector
{
template <class T>
class vector
{
public:
// 迭代器重命名
typedef T *iterator;
typedef const T *const_iterator;
// 无参数构造函数
vector() {}
// 获取当前有效数组长度
size_t size() const
{
return _finish - _start;
}
// 获取当前申请的内存空间大小
size_t capacity() const
{
return _endofstorage - _start;
}
// 改变vector的capacity
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldSize = size();
T *tmp = new T[n]; // 申请内存空间
// 如果_start不是空指针,就释放之前申请的内存空间
if (_start)
{
memcpy(tmp, _start, sizeof(T) * oldSize); // 复制旧数组数据
delete[] _start; // 释放就数组内存
}
// 更新
_start = tmp;
_finish = _start + oldSize;
_endofstorage = _start + n;
}
}
// 在指定位置之前插入数据
iterator insert(iterator pos, const T &x)
{
// 判断指定位置是否越界
assert(pos >= _start && pos <= _finish);
// 如果已分配内存已经被全部使用
if (_finish == _endofstorage)
{
size_t len = pos - _start; // 保存pos在旧数组中的位置
reserve(capacity() == 0 ? 4 : capacity() * 2); // 申请新内存空间
pos = _start + len; // 更新pos
}
// 插入
iterator i = _finish - 1;
while (i >= pos)
{
*(i + 1) = *i;
--i;
}
*pos = x;
++_finish;
return pos;
}
// 尾插
void push_back(const T &x)
{
insert(_finish, x);
}
// 支持列表初始化的构造函数
vector(initializer_list<T> il)
: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
// 申请内存空间
reserve(il.size());
// 将数据循环尾插
for (auto &e : il)
{
push_back(e);
}
}
// 析构函数
~vector()
{
// 如果指针不为空
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
// 判断数组是否为空
bool empty()
{
return _start == _finish;
}
// 尾删
void pop_back()
{
// 判断数组是否为空
assert(!empty());
--_finish;
}
// 迭代器实现
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
// 数组下标访问实现
T &operator[](size_t i)
{
assert(i < size()); // 判断下标是否超过数组长度
return _start[i];
}
// 删除pos位置的数据
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos >= _finish);
iterator i = pos + 1;
while (i < _finish)
{
*(i - 1) = *i;
++i;
}
--_finish;
return pos;
}
// vector 容器的迭代器范围构造函数,用于通过一个迭代器区间 [first, last) 来初始化 vector
// int arr[] = {1, 2, 3, 4};
// vector<int> v(arr, arr + 4); // 用数组的全部元素初始化 v
// vector<int> v2(v.begin() + 1, v.end() - 1); // 用 v 中 [1,3) 范围的元素初始化 v2
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//填充构造函数
// 创建一个包含 5 个 int 类型元素 10 的 vector
//vector<int> v1(5, 10); // 元素:10,10,10,10,10
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
//拷贝构造函数
vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
void swap(vector<T>& tmp)
{
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
//改变 vector 容器中元素的数量
void resize(size_t n, T val = T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
}
private:
iterator _start = nullptr; // 底层存储区的起始位置
iterator _finish = nullptr; // 最后一个有效元素的下一个位置
iterator _endofstorage = nullptr; // 底层已分配内存的末尾的下一个位置
};
}
测试
cpp
复制代码
#include <iostream>
#include <cassert>
#include "myvector.hpp"
using namespace std;
int main()
{
using myvector::vector;
cout << "[Test 1] default constructor" << endl;
vector<int> v;
cout << " size=" << v.size() << " capacity=" << v.capacity() << endl;
assert(v.size() == 0);
cout << " -> OK\n";
cout << "[Test 2] push_back and basic access" << endl;
for (int i = 1; i <= 5; ++i)
{
v.push_back(i);
cout << " pushed " << i << " size=" << v.size() << " capacity=" << v.capacity() << "\n";
}
assert(v.size() == 5);
for (size_t i = 0; i < v.size(); ++i)
{
assert(v[i] == (int)i + 1);
}
cout << " contents:";
for (auto x : v) cout << ' ' << x;
cout << "\n -> OK\n";
cout << "[Test 3] insert in middle" << endl;
auto it = v.begin() + 2; // insert before element 3
cout << " before insert size=" << v.size() << " capacity=" << v.capacity() << "\n";
v.insert(it, 99);
cout << " after insert size=" << v.size() << " capacity=" << v.capacity() << "\n";
assert(v.size() == 6);
assert(v[2] == 99);
cout << " contents:";
for (auto x : v) cout << ' ' << x;
cout << "\n -> OK\n";
cout << "[Test 4] copy constructor" << endl;
vector<int> vcopy(v);
cout << " original size=" << v.size() << " copy size=" << vcopy.size() << "\n";
assert(vcopy.size() == v.size());
for (size_t i = 0; i < v.size(); ++i) assert(vcopy[i] == v[i]);
cout << " -> OK\n";
cout << "[Test 5] assignment operator" << endl;
vector<int> vassign;
vassign = v;
cout << " assigned size=" << vassign.size() << "\n";
assert(vassign.size() == v.size());
for (size_t i = 0; i < v.size(); ++i) assert(vassign[i] == v[i]);
cout << " -> OK\n";
cout << "[Test 6] initializer_list constructor" << endl;
vector<int> vil = {10, 20, 30};
cout << " vil size=" << vil.size() << " contents:";
for (auto x : vil) cout << ' ' << x;
cout << "\n";
assert(vil.size() == 3);
assert(vil[0] == 10 && vil[1] == 20 && vil[2] == 30);
cout << " -> OK\n";
cout << "[Test 7] reserve and capacity growth" << endl;
size_t oldcap = v.capacity();
cout << " old capacity=" << oldcap << "\n";
v.reserve(oldcap + 10);
cout << " new capacity=" << v.capacity() << "\n";
assert(v.capacity() >= oldcap + 10);
cout << " -> OK\n";
cout << "[Test 8] resize larger and smaller" << endl;
v.resize(10, -1);
cout << " resized up size=" << v.size() << " capacity=" << v.capacity() << "\n";
assert(v.size() == 10);
for (size_t i = 6; i < 10; ++i) assert(v[i] == -1);
v.resize(3);
cout << " resized down size=" << v.size() << "\n";
assert(v.size() == 3);
cout << " -> OK\n";
cout << "[Test 9] pop_back" << endl;
cout << " before pop size=" << v.size() << "\n";
int last = v[v.size() - 1];
v.pop_back();
cout << " after pop size=" << v.size() << " popped=" << last << "\n";
assert(v.size() == 2);
cout << " -> OK\n";
cout << "[Test 10] iteration (const and non-const)" << endl;
int sum = 0;
cout << " vassign contents:";
for (auto &x : vassign) { sum += x; cout << ' ' << x; }
cout << "\n";
const vector<int> &cref = vassign;
int csum = 0;
for (auto it2 = cref.begin(); it2 != cref.end(); ++it2) csum += *it2;
cout << " sum=" << sum << " csum=" << csum << "\n";
assert(sum == csum);
cout << " -> OK\n";
// Print final contents of vassign
cout << "Final vassign contents:";
for (auto x : vassign) cout << ' ' << x;
cout << '\n';
cout << "ALL TESTS PASSED\n";
return 0;
}