C++ vector 的介绍和使用


一、vector 的介绍

  1. vector 是表示可变大小数组的序列容器
  2. vector 就像数组一样,也采用的连续空间来存储元素,这也意味着可以采用下标对 vector 的元素进行访问
  3. vector 与普通数组不同的是,vector 的大小是可以动态改变的
  4. 当 vector 需要重新分配大小时,其做法是:开辟一个新的连续空间,然后将全部元素移到这个空间当中,并释放原来的数组空间
  5. vector 分配空间策略:vector 会分配一些额外的空间以适应可能的增长,因此存储空间比实际需要的存储空间一般更大,以至于在末尾插入一个元素的时候是在 O(1) 的时间复杂度完成的。不同的平台实现的 vector 采用的策略可能不同
  6. 由于 vector 采用连续的空间来存储元素,与其他动态序列容器相比,vector 在访问元素的时候更加高效,在其末尾添加和删除元素相对高效,而对于不在其末尾进行的删除和插入操作效率则相对较低

二、vector 的使用

vector 常用接口介绍

构造函数

template <class T>
//构造一个某类型的空容器
vector<T>();
//构造一个含有 n 个 val 的某类型容器
vector<T>(szie_t n, cosnt T& val);
//拷贝构造某类型容器
vector<T>(const vector<T>& vtr);
//使用迭代器构造某个容器区间 [first, last) 的数据
template <class InputIterator>
vector<T>(InputIterator first, InputIterator last);

vector<int> vtr1;
vector<int> vtr2(10, 2);
vector<int> vtr3(vtr2);
string str = "hello";
vector<char> vtr4(str.begin(), str.end());
//vector 的嵌套(类似二维数组)
vector<vector<int>> vtr5(5, vector<int>(5, 100));

vector 大小与容量

  1. size()、max_size() 和 capacity()
size_t size()const;		//返回容器有效元素个数
size_t max_size()const;	//返回 vector 容器的最大有效元素容量
void capacity()const;	//返回容器最大容量

vector<int> vtr(10, 0);
cout << vtr.size();		//10
cout << vtr.capacity();	//
  1. reserve() 和 resize()

🌸reserve(n) 规则:

  1. 当 n > capacity 时,capacity = n

  2. 当 n <= capacity 时,什么也不做

🌸resize(n) 规则:

  1. 当 n > size 时,size = n,并将扩大部分的元素赋值 val

  2. 当 n <= size 时,size = n,并将多余部分元素删除

void reserve(size_t n);		//改变 capacity(容量)的大小
void resize(size_t n, const T& val = T());		//改变 size(有效元素)的大小

vector<int> vtr(10, 0);
vtr.reserve(20);	
vtr.resize(2);
cout << vtr.capacity() << ' ' << vtr.resize();	//20 2
  1. empty()
bool empty()const;	//判断当前容器是否为空

vector<int> vtr;
cour << vtr.empty();	//1

vector 的迭代器

  1. begin() 和 end()
//begin() 可以获得容器中第一个元素的正向迭代器
vector<T>::iterator begin();
vector<T>::const_iterator begin()const;

//end() 可以获得容器中最后一个元素的后一个位置的正向迭代器
vector<T>::iterator end();
vector<T>::const_iterator end()const;

//使用正向迭代器遍历
vector<int> vtr(5, 1);
for(vector<int>::iterator it = vtr.begin(); it != vtr.end(); it++) {
	cout << *it;
}	//11111

for(auto e : vtr) {
	cout << e;
}	//11111
  1. rbegin() 和 rend()
//rbegin() 可以获取容器中最后一个元素的反向迭代器
vector<T>::reverse_iterator rbegin();
vector<T>::const_reverse_iterator rbegin()const;

//rend() 可以获取容器中第一个元素的前一个位置的反向迭代器
vector<T>::reverse_iterator rend();
vector<T>::const_reverse_iterator rend()const;
  1. cbegin()、cend() 、crbegin()、crend()
//获取容器的 const 正向迭代器
vector<T>::const_iterator cbegin()const;
vector<T>::const_iterator cend()const;

//获取容器的 const 反向迭代器
vector<T>::const_reverse_iterator crbegin()const;
vector<T>::const_reverse_iterator crend()const;

vector 的增删查改

  1. push_back() 和 pop_back()
void push_back(const T& val);
void pop_back();

vector<int> vtr = {1, 2, 3};
vtr.pop_back();
vtr.push_back(10);
for(auto e : vtr) {
	cout << e << ' ';
}	//1 2 10
  1. insert()
//在 pos 位置前插入元素 val
vector<T>::iterator insert(vector<T>::iterator pos, const T& val);
//在 pos 位置前插入 n 个元素 val
void insert(vector<T>::iterator pos, size_t n, const T& val);
template <class InputIterator>
//在 pos 位置前插入某迭代器区间 [first, last) 的值
vector<T>::iterator insert(vector<T>::iterator pos, InputIterator first, InputIterator last);

//insert 返回的是插入的第一个元素在容器中的迭代器
vector<int> vtr = {1, 2, 3};
vector<int>::iterator it1 =  vtr.insert(vtr.begin(), 10);
cout << *it1;	//10
vector<int> vtr1 = {0, 1};
vector<int>::iterator it2 = vtr.insert(vtr.begin(), vtr1.begin(), vtr1.end());
cout << *it2 = 0;

🌸以上是按位置进行插入元素的方式,若要按值进行插入(在某一特定值位置进行插入),则需要用到 find 函数

template <class InputIterator, class T>
InputIterator std::find(InputIterator first, InputIterator last, const T& val);

vector<int> vtr = {1, 2, 3};
vector<int>::iterator it = std::find(vtr.begin(), vtr.end(), 2);
cout << *it = 2;

🌸关于 find() 函数:

  1. find 函数共三个参数,前两个参数确定一个迭代器区间(左闭右开),第三个参数确定所要寻找的值

  2. find 函数在所给迭代器区间寻找第一个匹配的元素,并返回它的迭代器,若未找到,则返回所给的第二个参数

  3. find函数是在算法模块(algorithm)当中实现的,不是 vector 的成员函数

  4. string 类里面的 find() 是 string 类内部实现的,而这里使用的 find() 是算法库里面的,不是 vector 的接口

#include <algorithm>	//find() 函数在该头文件中
vector<int> vtr = {1, 2, 3};
//在值 2 前面插入值10
vector<int>::iterator it = find(vtr.begin(), vtr.end(), 2);
vtr.insert(it, 10);
  1. erase() 函数
//删除 pos 位置所在的元素
vector<T>::iterator erase(vector<T>::iterator pos);

//删除区间 [first, end)
vector<T>::iterator erase(vector<T>::iterator first, vector<T>::iterator last);

//erase() 返回删除的最后一个元素的下一个元素的迭代器位置,如果容器的元素被删除完,则返回 end()

//1. 通过元素所在容器的位置(pos)删除
vector<int> vtr = {1, 2, 3};
vector<int>::iterator it1 = vtr.erase(vtr.bengin());
cout << *it1;	//2

vector<int>::iterator it2 = vtr.erase(vtr.bengin(), vtr.end());
cout << vtr.end() == it2;	//1

//2. 通过指定值删除容器中的元素(与 find() 函数配合)
//删除容器中值为 2 的元素
#include <algorithm>
vector<int> vtr = {1, 2, 3};
vector<int>::iterator it = std::find(vtr.begin(), vtr.end(), 2);
vtr.erase(it);
  1. assign() 函数
//删除原来容器内的元素,并将容器设置成 n 个 val 元素
void assign(size_t n, const T& val);

//删除原来容器的元素,并将容器设置成某个容器区间 [first, last) 的元素
template <class InputIterator>
void assign(InputIterator first, InputIterator last);

vector<int> vtr = {1, 2, 3};
vtr.assign(3, 10);
for(auto e : vtr) {
	cout << e << ' ';
}	//10 10 10

string str = "YW";
vector<char> vtr1 = {'Z', 'C'};
vtr1.assign(str.begin(), str.end());
for(auto e : vtr) {
	cout << e << ' ';
}	//Y W

vector 元素访问

  1. opeartor[ ] 和 at()
//operator[] 和 at() 作用一样
T& operator[](size_t pos);
const T& operator[](size_t pos)const;
T& at(size_t pos);
const T& at(size_t pos)const;

vector<int> vtr = {1, 2, 3};
cout << vtr[1] << ' ' << vtr.at(1);		//2 2
  1. front() 和 back()
T& front();		//返回 vector 第一个元素
const T& front()const;
T& back();		//返回 vector 最后一个元素
const T& front();

vector<int> vtr = {1, 2, 3};
cout << vtr.front() << ' ' << vtr.back();	//1 3
  1. data()
//返回容器第一个元素的指针(相当于返回 vector 的数组形式)
T* data();
const T* data()const;

vector<int> vtr = {1, 2, 3};
int* arr = vtr.data();
cout << arr[1];		//2

swap() 函数

//swap() 函数可以交换两个容器
void swap(vector<T>& vtr);

vector<int> vtr1 = {1, 2};
vector<int> vtr2 = {3, 4};
vtr1.swap(vtr2);

vector 迭代器失效问题

迭代器失效问题举例

🌸迭代器的主要作用就是让我们在使用各个容器时不用关心其底层的数据结构,而 vector 的迭代器在底层实际上就是一个指针。迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而指向的是一块已经被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃


实例一
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
	vector<int> vtr = { 1, 2, 3, 4, 5 };
	vector<int>::iterator pos = find(vtr.begin(), vtr.end(), 3);
	vtr.insert(pos, 10);
	vtr.erase(pos);
	cout << *pos;
	return 0;
}

运行上面的代码结果如下:

在这里插入图片描述

我们进一步调试:

在这里插入图片描述

🌸由上我们发现,当我们创建该容器时,该容器的 capacity 和 size 相同,所以当我们再使用 insert 插入时,其会重新找个堆区进行扩容,导致原来的地址 pos 失效,伪代码如下:

if(_capacity < _size + 1) {	//扩容
	int* tmp = new[_size + 1] int;
	data_copy(tmp, _vtr);	//将原来 _vtr 中的所有数据拷贝到 tmp 中
	delete[] _vtr;
	_vtr = tmp;
	_vtr[_size] = data;		//插入新的数据
	_capacity = _size + 1;
	_size++;
}

❗️注意:上面的代码是在 debug 版本运行的,在 debug 版本中,编译器会自动检测是否存在迭代器失效的问题,然后抛出异常。但是在 release 版本中,编译器并不会检测是否发生迭代器失效的问题:

在这里插入图片描述


实例二
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
	vector<int> vtr = { 1, 2, 3, 4, 5, 6};
	vtr.reserve(10);
	vector<int>::iterator pos = find(vtr.begin(), vtr.end(), 3);
	//在 3 前面插入 10
	vtr.insert(pos, 10);
	//删除 3 
	vtr.erase(pos);
	for (auto e : vtr) {
		cout << e;
	}
	cout << endl << *pos;
	return 0;
}

🌸这里我们在 release 版本运行,因为 debug 版本会自动检测出存在迭代器失效问题,上面代码在 release 版本运行结果如下:
在这里插入图片描述

🌸我们发现,其并没有按我们的需求删除元素 3,而是删除了元素 10,这是因为迭代器失效而导致的问题,具体情况如下:

在这里插入图片描述

🌸总结:在该代码中,我们本意是使用元素 3 的迭代器在原序列中 3 的位置插入一个 10,然后将 3 删除,但我们实际上获取的是指向 3 的指针,当我们在 3 的位置插入 10 后,该指针就指向了 10,所以我们之后删除的实际上是 10,而不是 3


实例三
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
	vector<int> vtr = { 1, 2, 3, 4, 5, 6};
	//删除容器中的偶数
	vector<int>::iterator it = vtr.begin();
	while (it != vtr.end()) {
		if (*it % 2 == 0) {
			vtr.erase(it);
		}
		it++;
	}
	return 0;
}

🌸当我们再 release 版本运行上述代码,会直接抛出异常(下面是 debug版本的报错信息):

在这里插入图片描述

🌸原因分析:

在这里插入图片描述

🌸不仅如此,在上述代码中,我们使用迭代器对容器遍历并对元素 3 和元素 5 进行判断


迭代器失效解决方法

🌸在使用迭代器时,永远要记得:每次使用前,对迭代器进行重新赋值

实例一和实例二解决方案
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
	vector<int> vtr = { 1, 2, 3, 4, 5 };
	vector<int>::iterator pos = find(vtr.begin(), vtr.end(), 3);
	vtr.insert(pos, 10);
	//对 pos 重新赋值
	pos = find(vtr.begin(), vtr.end(), 3);
	vtr.erase(pos);		//删除元素 3
	return 0;
}

🌸对于实例一和实例二,我们可以在插入元素 10 后重新对迭代器进行赋值,然后删除元素 2


实例三解决方案
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
	vector<int> vtr = { 1, 2, 3, 4, 5, 6};
	//删除容器中的偶数
	vector<int>::iterator it = vtr.begin();
	while (it != vtr.end()) {
		if (*it % 2 == 0) {
			it = vtr.erase(it);	//删除后就会获取下一个元素的迭代器
		}
		else {
			it++;	//是奇数则 it++
		}
	}
	return 0;
}

🌸对于实例三,我们可以接收 erase 函数的返回值(erase 函数返回删除元素的后一个元素的迭代器),并且控制代码的逻辑:当元素被删除后继续判断该位置的元素(因为该位置的元素已经更新,需要再次判断),只有元素为奇数时才会 ++ 迭代器


本篇文章到这里就结束啦,欢迎批评指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值