目录
1.vector<int>和vector<vector<int>>
1.1两者的区别
vector<int>表示的就是一个一维数组,这个一维数组的数据类型都是int类型的;
vector<vector<int>>表示的就是一个二维数组,这个数组的每一个元素都是vector<int>类型的,也就是说这个数组里面的每一个元素都是一个vector<int>的一维数组
1.2遍历的方法
我们下面这个代码里面还对于这个二维数组进行遍历,这个遍历我们使用了两个for循环;
其中这个里面包括了我们对于数组元素的修改,我们的修改提供了两个方式,第一个就是使用的这个[][]即方括号索引,我们也可以使用这个operator[],第一个是属于vector<int>的,第二个是属于int,两个方括号不是一个类里面的,两个方法是等效的;
2.vector模拟实现的准备
我们首先要构建出来一个基本的框架,方便我们进行后续的操作:其中这个里面的_finish就是到我们的这个真实数据的最后一个位置,类似于我们之前介绍的这个size,这个end_of_storage类似于我们之前介绍的这个capacity,就是这个空间大小,容量;
#pragma once
#include<assert.h>
#include<iostream>
using namespace std;
namespace bite
{
template<class T>
class vector
{
public:
typedef T* iterator;
void reserve(size_t n)
{
T* temp = new T[n];
memcpy(temp, _start, size());
delete[] _start;
_start = temp;
_finish = _start + size();
_end_of_storage = _start + n;
}
size_t size()
{
return _finish - _start;
}
size_t capacity()
{
return _end_of_storage - _start;
}
void push_back(const T& x)
{
//扩容判断语句
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
T& operator[](size_t n)
{
assert(n < size());
return _start[n];
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
void test01()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
}
}
3.reserve出现的问题及解决方案
我们在实现这个vector扩容的时候,这个里面会出现类似于下面的问题,就是我们的这个vector扩容是因为这个空间不够了,我们需要开辟新的空间,vector的底层使用的是内存池,但是我们这里直接使用这个new进行动态的开辟,因为即使是内存池,这个空间也是new出来的;
空间开辟完成之后,我们直接进行这个memcpy即进行拷贝,从_start开始,拷贝的数量就是这个size()空间大小,拷贝到这个temo位置上面,然后我们释放掉原来的空间;
我们让这个temp只想我们的_start,finish就是开始加上这个size()大小,capacity就是开始加上新开辟的空间n的大小,这个看似没有问题,实际上运行的时候就会报错,聪明的你发现问题了吗?
实际上,上面的这个问题就是因为这个_finish计算的时候需要去进行这个size()函数的调用,但是调用这个函数的时候,需要使用到这个finish-start,然而这个原意是让这个初始情况下的finish-start,但是这个实际上我们的finish调用的上一步,start已经被重新赋值,这个时候就违背了我们的意愿要求;
这个时候,有两个解决方案,就是先更新这个finish,再对于这个start进行更新在,这个是可以的,但是这个方式我们不推荐,因为看着很别扭,都知道这个start,finish,storage,这个时候你的顺序不对看着就难受;
这个时候我们可以使用下面的方法,就是提前记录下来这个size的空间大小,然后进行使用:
4.遍历vector的三种方式
第一种就是用这个普通的for循环进行遍历,使用下标进行容器元素的遍历;
第二个方式就是使用这个迭代器的方式,这个里面的这个vector<int>::iterator表示的实际含义就是这个iterator是一个迭代器,这个迭代器是用来对于这个vector<int>容器里面的元素进行遍历的;
第三个就是使用范围for进行遍历,使用这个auto进行这个类型的识别即可;
5.关于typename的使用
我们这个vector的使用里面是如何引入我们的这个typename的呢,首先是这个我们想要让这个vector容器里面存储这个double类型的数据,同样是对于数据的打印,我们想要这个函数打印这个double类型的数据,这个时候的我们的这个print_vector这个函数里面的参数的数据类型就是这个const vector<T>这个时候,参数会根据我们的传参进行判断,例如我们传递这个v就是vector<int>类型,这个时候的T就是int,但是当我们传递这个vector<double>的时候,这个T就是double类型了,这个就是模版;
因为这个时候函数的参数里面是这个const类型的数据,因此这个迭代器需要更改为这个const_iterator,这个要求我们需要实现const版本的函数:
这个时候就有了需要typename的地方,就是因为这个时候的vector<T>没有进行实例化,编译器无法区分这个const_iterator是静态成员变量还是迭代器,不会进入这个没有实例化的模版里面取东西,因此我们需要加上这个typename关键字;
这个vector<T>就是没有实例化,像这种vector<int>明确的给出来这个容器里面的数据类型的,就是已经进行了实例化,这个就是有无实例化的说明;
当然这个时候,貌似这个it的类型的名字很长,这个时候才是我们的auto真正的进行大展身手的时候,我们前面见到的这个aotu替换数据类型效果都不是很显著,但是这个地方的替换就很明显,凸显了auto的默认类型识别的功能;
6.insert导致的迭代其实失效问题
6.1因为扩容导致的迭代器失效
下面的这个我们通过调试就会发现,当这个需要进行扩容的时候,这个时候pos的位置应该已经变了,但是我们的这个程序里面的这个pos依然是指向的原来的空间,这个时候就是迭代器失效了,观察到的现象就是我们进行调试的时候,当这个end=pos之后,继续进行下去,按理说这个循环应该停止,但是这个时候因为我们的pos执行原来的空间,因此这个时候的循环会继续进行下去;
我们针对于上面的情况,解决方案就是我们的这个扩容的时候记录下来这个pos相对于start的相对位置,然后reserve之后对于这个pos新的位置进行更新:
6.2因为插入数据倒置的迭代器失效
我们想在第二个位置插入数据20,插入之后对于这个位置的数据进行*10的操作,但是我们运行之后发现这个未知的数据并没有按照我们的要求乘上10,这个也是一个迭代器的失效问题;
这个时候我们的做法就是把这个更新的位置记录下来,然后按照我们的需求对于这个pos的下一位置进行*10操作;
这个时候想要记录这个pos,我们就需要调用这个insert之后有返回值,这个时候我们需要对于这个insert进行改写,增加返回值: