(超详细 + 实例演示) C++的STL学习 5 : vector的学习, 接口的使用和认识

之前我们学习了STL的字符串string类, 不知道大家用起来方不方便, 反正我是用起来非常奈斯的~.
这次我们来学习另外一个非常非常重要的容器 — vector — 可变的数组容器, vector使用起来也是十分的方便, 解决了之前要考虑数组长度, 空间够不够用的问题…那么话不多说, 直接开始吧. 耐心看完 你一定有所收获~

一. vector的认识

吹了半天, 那么vector到底是什么呢.?

下面进行简单介绍:

  • vector是表示可变大小数组的序列容器
  • 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
  • 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。
  • vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。
  • 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。
  • 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。

二. vector常用接口说明

vector实际上是一个泛型类, 为了容纳不同类型的数据, 所以使用时必须显式实例化指定要装的数据类型

1. vector的构造函数

Construct接口说明
vector()构造一个空的vector
vector (n , val)用n个数据val构造一个vector
vector (const vector& x)拷贝构造
vector (InputIterator first, InputIterator last)用两个迭代器表示的区间构造

这里顺便说一下赋值:
vector& operator= (const vector& x)
用一个vector对象给另一个已经存在的vector对象赋值, 是深拷贝

下面给出测试代码:

void test() {
	//vector构造
	vector<int> v1;
	vector<char> v2;

	vector<int> v3(10, 1); //10个1
	vector<int> copy(v3); //拷贝构造


	int arr[] = { 1, 2, 3, 4, 5 };
	vector<int> v4(arr, arr + 5);

	//赋值: 深拷贝
	v1 = v4;
	//拷贝构造
	vector<int> v5 = v4;
	}

调试结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 迭代器和访问

和string的迭代器使用方法一样, STL的容器保证了迭代器的使用方法一致, 降低了我们的使用成本

Iterators接口说明
begin指向第一个元素的位置
end指向最后一个元素的下一个位置
rbegin反向迭代器, 指向最后一个元素
rend反向迭代器, 指向第一个元素前一个位置
cbegin
cend
crbegin下面这四个都是const迭代器, 与上面对应, 无法修改内容
crend
front获取第一个元素
back获取最后一个元素

front和back 的返回值都是引用类型, 所以可以直接修改vector的内容

接下来我们来遍历上面构造的vector

struct A {
	int _a = 1105;
};

void test() {
	//vector构造
	vector<int> v1;
	vector<char> v2;

	vector<int> v3(10, 1); //10个1
	vector<int> copy(v3); //拷贝构造


	int arr[] = { 1, 2, 3, 4, 5 };
	vector<int> v4(arr, arr + 5);

	//赋值: 深拷贝
	v1 = v4;
	//拷贝构造
	vector<int> v5 = v4;


	//迭代器
	vector<int>::iterator it = v4.begin();
	while (it != v4.end()) {
		cout << *it << " ";
		it++;
	}
	cout << endl;

	vector<char>::iterator it2 = v2.begin();
	while (it2 != v2.end()) {
		cout << *it2 << " ";
		it2++;
	}
	cout << endl;

	//自定义类型
	vector<A> v6(5, A());
	vector<A>::iterator it3 = v6.begin();
	while (it3 != v6.end()) {
		cout << (*it3)._a << " ";
		it3++;
	}
	cout << endl;


	//反向迭代器
	vector<int>::reverse_iterator rit = v4.rbegin();
	while (rit != v4.rend()) {
		cout << *rit << " ";
		rit++;
	}
	cout << endl;


	//范围for
	for (const auto& val : v4) {
		cout << val << " ";
	}
	cout << endl;

	//operator[]
	for (int i = 0; i < v4.size(); i++) {
		cout << v4.operator[](i) << " ";
		cout << v4[i] << " ";
	}
	cout << endl;


	//operator[]越界: assert错误
	//at() 越界: 异常
	cout << v4[20] << endl;
	cout << v4.at(20) << endl;

}

运行结果如下:
在这里插入图片描述

上述代码运行时会报错, 大可不必惊慌, 那是最后两行代码, 为了说明operator[] 和 at() 越界时的不同反应,

operator[ ] 越界: assert错误
at() 越界: 异常

3. 容量相关

Capacity接口说明
size数组的有效字符长度
capacity当前数组能存放的最大字符个数(空间大小)
empty检测字符串是否为空, 为空返回true, 否则返回false
resize设置/修改有效元素个数size
reserve设置/修改容量capacity的大小
shrink_to_fit把容量缩小到合适

resize(n): n > size 在[size, n) 赋默认值:0,’\0’,nullptr,自定义类型调默认构造
原则: 内置类型初始化为类型对应的0, 自定义类型调默认构造

resize(n, val) : 一定影响size: size = n
n > capacity 增容
当 n > size时val才有效

下面给出测试代码:

void test2() {
	vector<int> v(3, 1);
	vector<char> v2(10, 'a');
	vector<A> v3(5, A());

	cout << "v size: " << v.size() << endl;
	cout << "v2 size: " << v2.size() << endl;
	cout << "v3 size: " << v3.size() << endl;

	cout << "v max size: " << v.max_size() << endl;
	cout << "v2 max size: " << v2.max_size() << endl;
	cout << "v3 max size: " << v3.max_size() << endl;
	

	vector<int> v4;
	vector<char> v5;
	cout << v4.capacity() << endl;
	cout << v5.capacity() << endl;

	v4.push_back(1);
	cout << v4.capacity() << endl;
	v4.push_back(2);
	cout << v4.capacity() << endl;
}

运行结果如下:
在这里插入图片描述

resize的测试

void test3() {
	vector<int> v;
	
	v.resize(5);

	vector<char> v2;
	v2.resize(5);

	vector<int*> v3;
	v3.resize(5);

	vector<A> v4;
	v4.resize(5);

	vector<char> v5(10, 'a');

	v5.resize(20, 'b');

	v5.resize(21, 'c');

	v5.resize(5, 'd');
}

调试结果如下:

v.resize(5):

v2.resize(5):

在这里插入图片描述
v3.resize(5):

在这里插入图片描述

v4.resize(5): 自定义类型, 调默认构造

在这里插入图片描述

v5(10, 'a') : 构造v5

在这里插入图片描述

v5.resize(20, 'b') : 在10 - 19 补了'b'

在这里插入图片描述

v5.resize(21, 'c') : 在最后补了一个c

在这里插入图片描述
v5.resize(5, 'd') : 从第五个元素截断, 给的'd'值没有用

在这里插入图片描述

reserve: 只改变capacity, 不改变size
vector的 reserve 只会增容, 不会缩容

接下来我们看看VS中的vector的增容方式:

void printCap() {
	vector<int> v;
	//vector增容: PJ版本: 1.5倍    SGI: 2倍
	size_t cap = v.capacity();
	cout << "v capacity: " << v.capacity() << endl;
	for (int i = 0; i < 150; i++) {
		v.push_back(i);
		if (cap != v.capacity()) {
			cap = v.capacity();
			cout << v.capacity() << endl;
		}
	}
}

运行结果如下:
在这里插入图片描述
我们可以看到容量大致是以1.5倍增容的

reserve的测试:

void test4() {
	vector<int> v;
	cout << "cap: " << v.capacity() << endl;

	//reserve: 不会影响size
	v.reserve(100);
	cout << "cap: " << v.capacity() << endl;

	//vector的reserve只能增容, 不会减小容量
	v.reserve(10);
	cout << "cap: " << v.capacity() << endl;

	v.resize(35);
	//减小容量, 用多少剩多少
	v.shrink_to_fit();
	cout << "cap: " << v.capacity() << endl;

}

运行结果如下:

在这里插入图片描述
经过大量测试, 我发现shrink_to_fit()会把capacity缩减至有效元素的个数

4. 内容修改相关

Modifiers接口说明
push_back尾插一个元素
pop_back尾删一个元素
insert在pos位置前插入数据
earse删除pos位置数据
assign赋值
swap交换两个vector的数据空间
clear清空数组

要注意的是: vector的 insert 和 erase 都是要用迭代器指定pos位置

插入删除的各种接口用以下代码说明: (结果都写在注释中)

void test5() {
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);

	v.pop_back();
	v.pop_back();
	v.pop_back();


	//insert() 位置必须由迭代器指定
	v = { 1, 2, 3 , 4 };
	v.insert(v.end(), 5);		 // 1 2 3 4 5
	v.insert(v.begin(), 0);		 // 0 1 2 3 4 5
	v.insert(v.begin() + 2, 10); // 0 1 10 2 3 4 5
	v.insert(v.end() - 1, 20);	 // 0 1 10 2 3 4 20 5

	v.insert(v.begin(), 3, -1);  //头插3个-1

	vector<int> v2(3, 9);
	//头插一个v2的区间[begin, end)
	v.insert(v.begin(), v2.begin(), v2.end());// 9 9 9 ~

	//迭代器区间: 左闭右开
	//迭代器类型: 不一定是vector迭代器, 
	//只要迭代器解引用后类型和vector存放类型一致即可  (数组是一个天然的迭代器)
	int arr[] = { 5, 5, 5 };
	v.insert(v.end(), arr, arr + 2); // ~ 5 5  


	//erase 删除
	v = { 0, 1, 2, 3, 4, 5, 6, 7, 8 ,9 };
	v.erase(v.begin()); //头删  1 ~ 9
	v.erase(v.end() - 1); // 尾删  end位置没有元素 1 ~ 8
	v.erase(v.begin() + 1, v.end() - 1);// 只剩头尾元素 1 8

}

三. 迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了 封装
因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器, 程序可能会崩溃)。

  1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、 push_back等

比如:

	//resize
	v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	it = v.begin();

	v.resize(5);
	cout << *it << endl; //减小容量, 没问题

	v.resize(9);
	cout << *it << endl; // 没超过cap, 不增容, 没问题

	v.resize(10);
	cout << *it << endl; //增容, 迭代器失效

	// reserve
	v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	it = v.begin();

	v.reserve(5);
	cout << *it << endl; //不会减小容量, 没问题

	v.reserve(50);
	cout << *it << endl; // 增容, 失效

2. 插入删除引起迭代器失效

	//1. 插入导致
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
		cout << *it << " ";  //插入之后可能会增容, 导致失效
	}
	cout << endl;

	//2.
	v.clear();
	it = v.begin();

	v.insert(it, 0);     //插入没问题
	cout << *it << endl; //这里空间已经没了..

	// 3. 删除导致
	v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	it = v.end() - 1; // 指向9

	v.erase(it);
	cout << *it << endl; //位置越界

	// 4. 
	v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	it = v.begin(); // 指向1

	v.erase(it);
	cout << *it << endl; //位置错位, 也不允许访问 (PJ版)

迭代器失效的问题很严重, 而解决的方法很简单 : 重新获取的迭代器即可

所以大家在使用迭代器涉及到插入删除等引起空间变化的操作时, 尽量要重新获取一下迭代器, 防止出现越界或者野指针的问题, 导致程序崩溃

vector的接口就说到这里了~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殇&璃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值