模拟实现vector
- 1.基本概念
- 2.vector()默认构造函数
- 3.size()
- 4.capacity()
- 5.empty()
- 6.reverse
- 7.push_back()
- 8.pop_back()
- [9.operator[ ]](#9.operator[ ])
- 10.resize()
- 11.insert()
1.基本概念
上一节我们讲了vector的概念以及常用的接口,这一节我们讲一下它的实现,它的底层其实就只有三个成员变量:_start,_finish,_end_of_storage。_start指向目前使用空间的头,_finish指向目前使用空间的尾,_end_of_storage指向目前可用空间的尾。通过这个三个指向就可以完成我们vector的实现,可以看一下下面的图来简单理解一下
cpp
//基本结构
#include <iostream>
using namespace std;
namespace lnb
{
template<class T>
class vector
{
public:
typedef T* iterator;//迭代器类型,指针本身就可以做迭代器类型
private:
iterator _start;//指向目前使用空间的头
iterator _finish;//指向目前使用空间的尾
iterator _end_of_storage;//指向目前可用空间的尾
};
}
2.vector()默认构造函数
cpp
vector()//默认构造函数
:_start(nullptr),//全部设为nullptr指针
_finish(nullptr),
_end_of_storage(nullptr)
{}
这是默认的构造函数,不需要传任何参数,把指针全部设为nullptr即可。后续的其他构造函数,建立在其他的成员函数上写会更方便,先继续往后看
3.size()
cpp
iterator begin()//迭代器的开始
{
return _start;
}
iterator end()//迭代器的结束
{
return _finish;
}
size_t size()const//vector的大小
{
return end() - begin();
}
这边在实现size()的基础上顺便把迭代器的开始和结束也一起实现了,这样再写size()可以看上去更加的简便
4.capacity()
cpp
typedef const T* const_iterator;
const_iterator begin()const//针对于const类型的对象
{
return _start;
}
const_iterator end()const//针对于const类型的对象
{
return _finish;
}
size_t capacity()const//vector容量大小
{
return _end_of_storage - begin();
}
capacity()也是同样的道理,同时也把const迭代器给一起实现了,这边主要是capacity()被const类型修饰了,那么调用的begin()就需要也是被const修饰的,capacity()理解见下图
5.empty()
cpp
bool empty()const//判空
{
return begin() == end();
}
这个函数是用来判断vector是否为空的,我们直接使用了_start和_finish,因为当它们都为nullptr空时才会相同
6.reverse
cpp
void reverse(size_t n)//预留空间,提前扩容用的
{
if (n > capacity())//保证n是大于现有空间的
{
size_t old_size = size();
//1.先开辟新空间
T* new_start = new T[n];//这边暂时不处理异常
//2.将数据拷贝到新空间去
for (int i = 0; i < size(); i++)
{
//这边不能使用memcpy这种直接内存拷贝的函数
new_start[i] = _start[i];
}
//3.释放空间
delete[] _start;
//4.修改指向
_start = new_start;
_finish = new_start + old_size;
_end_of_storage = new_start + n;
}
}
reverse主要是为了提前预留空间,来防止后续添加元素造成的多次扩容从而效率低下,具体的流程:先开辟新的空间→拷贝旧的数据到新空间→释放旧空间→修改指向。其中最需要注意的是拷贝元素的过程,不能直接使用内存拷贝函数,假设我们vector的元素是一个string,又或者是一个vector呢?
我们来看图理解一下,当两个vector中的string中的_str指向相同的空间时会发生什么呢?在析构的时候,old_vector底层会释放开辟的空间,而空间中的是string类型,也需要调用它的析构函数,在string的析构中会把_str指向的空间释放掉,到这也什么问题,但是当new_vector释放时呢,它也会需要释放_str指向的空间,但是已经释放过一次了呀,这时候又释放就会造成程序崩溃。所以说使用memcpy这种函数来拷贝,其实本质上也是一种浅拷贝,因此我们需要使用循环赋值才能改变这个情况,当使用循环赋值后,会自动调用string的赋值重载函数,这样str指向的空间就不是同一个了。
7.push_back()
cpp
void push_back(const T& val)//尾插
{
if (_finish==_end_of_storage)
{
//我们扩容以2倍增长
reverse(capacity() == 0 ? 2 : capacity() * 2);
}
*_finish = val;
_finish++;
}
我们的尾插也非常的方便,只不过是要注意一下空间够不够,不够就需要进行扩容
8.pop_back()
我们先来看一下下面的代码,尾删这样写对吗
cpp
//!!!error code
void pop_back()//尾删
{
assert(!empty());//保证有数据
_finish--;
}
对于内置类型确实没有问题,但是对于string这种就不行了,会造成内存泄露,因此我们需要将其进行释放
cpp
void pop_back()//尾删
{
assert(!empty());//保证有数据
_finish->~T();//需要调用对应元素的析构,不然会造成内存泄露
_finish--;
}
9.operator[ ]
cpp
T& operator[](size_t n)//像数组一样去访问
{
assert(n < size());
return _start[n];
}
//针对const类型的vector
const T& operator[](size_t n)const
{
assert(n < size());
return _start[n];
}
10.resize()
cpp
void resize(size_t n,const T& val=T())
{
//要分三种情况,真正有作用的就两种情况
if (n >size())
{
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
else if(n<size())
{
size_t distance = size()-n;//计算出多了多少元素
for (int i = 0; i < distance; i++)
{
_finish->~T();//这边要记得释放,不然对于自定义类型会内存泄露
_finish--;
}
}
else
{
return;
}
}
我们的resize要分三种情况,分别是大于等于小于n,大于n的需要减少vector的大小;小于n的需要增大vector的大小,用val来填充;而等于n什么都不用做
11.insert()
cpp
iterator insert(iterator position, const T& val)//插入
{
//迭代器失效问题,因此需要保存迭代器位置
size_t pos = position-_start;
//1.先查看是否需要扩容
if (_finish == _end_of_storage)
{
reverse(capacity() == 0 ? 2 : capacity() * 2);
position= _start + pos;//扩容后position不在原来的空间了
}
//2.先进行元素的移动,将插入位置留出
iterator end = _finish-1;
while (end >= position)
{
*(end + 1) = *end;
end--;
}
//3.放入元素
*position = val;
_finish++;
return position;
}