Bootstrap

【C++】vector容器的详解(附模拟实现源码)


秃头侠们好呀,今天来说 vector

vector简介

vector查看文档

  1. vector表示可变大小数组序列的容器。
  2. 像数组一样,vector也采用的连续存储空间来存储元素。意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
  3. vector使用动态分配数组来存储它的元素。当新元素插入时候,如果空间不够,将会开辟一个新数组,新数组的空间大小一般是原来的2倍左右,然后把元素都移到新数组再把你插入的数组插进来。
  4. vector支持随机访问,所以访问元素比较高效,尾插尾删也很高效,但是如果在头插头删或者其他位置的插入删除,效率就很低,因为要移动大量数据。当然如果经常需要插入删除我们会使用像list这样的容器。

vector的使用

vector的定义

(constructor)构造函数声明接口说明
vector()(重点)无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造
#include<iostream>
#include<vector>

using namespace std;

int main()
{
	vector<int> v1;//无参构造
	vector<int> v2(3, 10);//用3个10构造
	vector<int> v3(v2.begin(), v2.end());//用一段迭代器区间初始化
	vector<int> v4(v3);//拷贝构造

	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	vector<int> v5(arr, arr+sizeof(arr) / sizeof(arr[0]));//也相当于一段迭代器区间

	vector<int>::iterator it = v5.begin();

	for (; it < v5.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;

	for (auto e : v5)
	{
		cout << e << " ";
	}
	cout << endl;

	for (int i = 0; i < v5.size(); i++)
	{
		v5[i] += 1;
		cout << v5[i] << " ";
	}
	cout << endl;

	return 0;
}

vector iterator 的使用

正向迭代器:
begin() 获取第一个数据位置
end()获取最后一个数据的下一个位置
反向迭代器:
rbegin() 获取最后一个数据位置
rend()获取第一个数据的前一个位置

迭代器都是左闭右开!
在这里插入图片描述

int main()
{
	// 使用push_back插入4个数据
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	// 使用迭代器进行遍历打印
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	// 使用迭代器进行修改
	it = v.begin();
	while (it != v.end())
	{
		*it *= 1;
		++it;
	}
	// 使用反向迭代器进行遍历再打印
	vector<int>::reverse_iterator rit = v.rbegin();
	while (rit != v.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
	
	return 0;
}
void PrintVector(const vector<int>& v)
{
	// const对象使用const迭代器进行遍历打印
	//只可打印不能修改
	vector<int>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

vector 空间扩容问题

size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变vector的size
reserve (重点)改变vector的capacity

注意

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。不要以为都是2倍增长的(vs是PJ版本STL,g++是SGI版本STL)
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问
    题。
  • resize在开空间的同时还会进行初始化,如果开辟的比原来小则会删除后面的数据,影响size。
  • reserve只会增大capacity如果再减少,capacity不会改变,这样是防止一会儿你又要扩容。

在这里插入图片描述

vector 增删查改

push_back(重点)尾插
pop_back (重点)尾删
find查找(注意这个是算法模块实现,不是vector的成员接口)
insert在pos之前插入val
erase删除pos位置的数据
swap交换两个vector的数据空间
operator[] (重点)像数组一样访问

at()函数和operator[]一样,都可以得到相应的下标的值,区别是at()会对边界做检查(如果有错会抛异常),而[]不会。

int main()
{
	int a[] = { 1, 2, 3, 4 };
	vector<int> v(a, a + sizeof(a) / sizeof(int));
	// 使用find查找3所在位置的iterator
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	// 在pos位置之前插入30
	v.insert(pos, 30);
	vector<int>::iterator it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;
	pos = find(v.begin(), v.end(), 3);
	// 删除pos位置的数据
	v.erase(pos);
	it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

vector 迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T*。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,变成野指针了,如果继续使用已经失效的迭代器,程序可能会崩溃。

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

那么像insert的操作为什么有可能会导致迭代器失效?

因为如果插入需要扩容,那么会新开一片空间,之前指向原数组pos位置的空间被释放,此时我们再对此位置解引用不就是对野指针解引用非法访问了嘛!
(如果空间足够,没有扩容那没事)

那么像erase的操作为什么有可能会导致迭代器失效?

其中一个原因是因为可能有的编译器会缩容,再开一片空间,那么就和上面的情况一样了(但一般不会缩容)。
另一种就是如果pos是最后一个元素,删除后,pos就是end的位置了,而end位置是没有元素的,那pos不就失效了。
还有一种情况就是,erase删除元素后,后面的元素会前移,pos的意义就不再是指向你删除的元素了,现在是指向下一个元素了。

所以迭代器失效解决办法:⭐在使用前,对迭代器重新赋值

vector模拟实现

vector模拟实现源码

使用memcpy拷贝问题

int main()
{
vector<string> v;
v.push_back("1111");
v.push_back("2222");
v.push_back("3333");
return 0;
}

这里需要扩容,出现问题了!

当模拟实现vector中的reserve接口中,使用memcpy进行拷贝会有问题。
1、memcpy是浅拷贝(内存的二进制格式),将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。
2、 如果拷贝的是内置类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
在这里插入图片描述
结论: 如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

动态二维数组

跟我们在C语言学的二维数组一样,vector也可以表示二维数组

vector<vector<int>>
vector<vector<double>>
vector<vector<string>>

在这里插入图片描述


⭐感谢阅读,我们下期再见
如有错 欢迎提出一起交流

;