我们模拟实现vector不是为了造更好的轮子,而是通过模拟更加理解vector这个容器
我们模拟实现vector不是为了造更好的轮子,而是通过模拟更加理解vector这个容器
我们模拟实现vector不是为了造更好的轮子,而是通过模拟更加理解vector这个容器
目录
-------------------------------------------------------
第一步:创建两个头文件
对于模拟实现vector,首先我们先建立两个文件,一个头文件vector.h用来装载需要的函数,另一个头文件 test.cpp用来测试
第二步:设定各种初始函数
其实对于vector 我们目前大体上是知道模板这个东西的。
备注:不知道模板的小伙伴可以看我的这篇文章c++模板简介_幻荼的博客-CSDN博客
同时我们一般使用三个指针来对模板数组进行构造
他们分别是start,finish,endofsoage(我这里是按照官方的命名方式)
简单来说就是这个(我也不知道为什么要这样命名,大佬这样做一定有他的道理)
namespace bit//我这里自己命名了一个空间bit,你按照自己的喜欢命名就好
{
template<class T>
class vector
{
public:
typedef T* iterator;//const和非const两个迭代器
typedef const T* const_iterator;
vector()
:_start(nullptr)//初始化
,_finish(nullptr)
,_endofstoage(nullptr)
{}
private:
iterator _start;//起始位置
iterator _finish;//size
iterator _endofstoage;//capacity
};
}
第三步:实现尾插(push_back)
其实尾插很简单,我们只需要考虑一个问题,就是容量的问题。
对于任何数据的改动,我们都需要知道究竟size和capacity的关系改动后是如何。
如果size>=capacity那么我们就需要进行扩容
void push_back(const T& x)
{
if (_finish == _endofstoage)//判断size!=capacity
{
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;//扩容
reserve(newCapacity);//通过reseve将旧空间的数据拷到新空间
}
*_finish = x;//尾插
++_finish;//size++
}
因为是自己模拟实现vector,所以vector中的reserve也需要我们自己实现
备注:对于不了解原版的reseve以及size和capacity的小伙伴可以看我这篇文章
c++:reserve和resize简介和区别_幻荼的博客-CSDN博客
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* tmp = new T[n];//开辟新空间
if (_start)//如果是空数据就不用拷贝和删除了
{
memcpy(tmp, _start, sizeof(T) * size());//拷贝数据
delete[]_start;//删除旧空间
}
_start = tmp;//指向新空间
}
_finish = _start + sz;
_endofstoage = _start + n;
}
备注:不了解memcpy的小伙伴可以看我这篇文章了解与模拟实现memmove和memcpy_幻荼的博客-CSDN博客
不了解new的小伙伴可以看我这篇文章
第四步:打印数据
我们先在头文件,vector.h里面push_back一些数据进入
void test_vector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);//我这里插入5个是因为我扩容是从4开始的,我要测试一下扩容是否成功
}
对于输出数据我这里用两种方式,一种范围for,一种迭代器,至于下标+[]的方式就不做演示了。
对于迭代器的方式,我们弄个begin和end然后while循环一下就好
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
auto it = v.begin();//vector<int>::iterator it = v.begin()简化一下,这是原版
while (it != v.end())//it指向最开始,当it!=最后一个,那么while就一直下去
{
cout << *it << " ";
it++;
}
然后范围for
for (auto e : v)
{
cout << e << " ";
}
最后再test.cpp中对该函数进行测试
int main()
{
bit::test_vector1();
return 0;
}
结果如下:
第五步:尾删(pop_back)
.....实在没什么需要讲的
void pop_back()
{
if (_finish > _start)
{
--_finish;
}
}
第六部:resize的实现
因为resize有两种情况
第一种:n>capacity,需要我们扩容(同时n>size还需要赋初始值);
第二种:n<size,需要我们缩容;
void resize(size_t n,const T& val=T())
{
if (n > capacity())//扩容
{
reserve(n);
}
if (n > size())//赋值
{
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
else//缩容
{
_finish = _start + n;
}
}
备注:不了解resize和reserve的小伙伴看我的这篇文章c++:reserve和resize简介和区别_幻荼的博客-CSDN博客
第七步:insert的模拟实现(迭代器失效)
实际上前面的六步都是没有什么难度的,大家也都能很容易的写出来,但我本人学习的时候再insert和erase这里跌了一个坑,弄了很久才想明白。
这也就牵扯到一个叫做”迭代器失效的问题”。
首先先来看一个我最开始的错误代码
void insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);//检查
if (_finish == _endofstoage)//扩容
{
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
iterator end = _finish-1;//挪动数据
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
}
实际上是这么个情况:我让end=末尾数据的-1的位置,让后依次挪动数据,当最后一次end=pos的位置数据被挪动,就是v[1]=v[0],然后end--=-1,最后将数据插入进pos的位置。
然后接下来就有一个很神奇的事情
当我调试插入4个数据(就是我扩容的临界值) ,编译器,崩了
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.insert(v.begin(), 0);
for (auto e : v)
{
cout << e << " ";
}
然后我插入5个值,他又好了
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.insert(v.begin(), 0);
for (auto e : v)
{
cout << e << " ";
}
然后我就以为是我扩容那一段写错了,对照着各路扩容代码改了半天,结果是迭代器失效
最根本的原因是在扩容reserve的这里
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* tmp = new T[n];//罪魁祸首
.........
我们在使用reserve扩容的时候,更新了_start,_finish,_endofstorage到新空间,但是唯独没有更新pos,pos还是指向了原先被释放的旧空间
这是开始没扩容的时候
这是扩容后
大家可以看到前三个已经到了新空间,而pos还是在原先的旧空间
这就是非常经典的迭代器失效问题
解决也很简单,只需要更新一下pos就好
void insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstoage)
{
size_t n = pos - _start;//新加入,算相对路径
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
pos = _start + n;//新加入,把相对路径给新空间的pos
}
iterator end = _finish-1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
}
结果如下,就没问题了
既然讲都讲到了迭代器失效,那我们再看一个我模拟实现的时候遇到的迭代器失效问题。
在偶数前面都插入一些数字,假如20.
void test_vector3()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)//判断偶数
{
v.insert(it, 20);
}
it++;
}
for (auto e : v)
{
cout << e << " ";
}
}
然后啪的一下,很快啊,一个错误给我报了出来
经过调试我发现,20插入了v[1]之后,it++这个地方出现了问题,
经过了insert扩容之后,it已经不在_start和_finish的区间了
看到这个朋友们就会有疑问
我不是已经在insert里面更新了pos吗,为什么it把值传给pos之后,it还会指向旧空间呢?
答案也很简单
pos这里是值传递,形参不改变实参
那我加个&是否可以呢?
如果加了&有些地方就会报错,而且我们是模拟实现vector,官方里面都没加&,我们最好也不要加
那有些小伙伴可能又要说了
那么我们开一个足够大的空间,让他不扩容不就好了吗?
让我们试试
void test_vector3()
{
vector<int> v;
v.reserve(10);//新加入
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.insert(it, 20);
}
it++;
}
for (auto e : v)
{
cout << e << " ";
}
}
啪的一下很快啊,编译器又把报错甩给我了
这里就是另外一个迭代器失效的问题了
我们在每次it插入之后,_start,_finish,_endofstorage都更新了,这就导致我们it每次++,都是从
v[1]->v[2],然后一直在v[2]处插入数据
所以:
insert以后虽然没有扩容,it也没有成为野指针,但是it指向的意义已经发生改变,所以这也是迭代器失效
这两个问题,实际上也很好解决,将insert的值给it,然后it+2就行了
void test_vector3()
{
vector<int> v;
v.reserve(10);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it=v.insert(it, 20);//将insert的值给it,然后it+2
++it;
}
it++;
}
for (auto e : v)
{
cout << e << " ";
}
}
}
总结:vector迭代器失效有两种
1.扩容,缩容,导致野指针
2.迭代器指向的位置意义变了
系统越界机制检查——不一定检查得到
编译实现机制检查——相对严格
第八步:erase的模拟实现
对于已经实现的insert,erase只需要在insert的基础上更改一下就可以了
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}
实际上就是这样:
第九步:swap函数与重载
经过以上几步,我们已经吧vector常用的函数功能实现的差不多了,下面就是对运算符等一系列资源进行重载
对于重载,我们这里直接介绍一种资本家写法
vector<T>& operator=(vector<T> v)
{
this->swap(v);
return *this;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finfish);
std::swap(_endofstoage, v._endofstorage);
}
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstoage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
this->swap(tmp);
}
简单来说,就是我先创建一个和你一样大的vector
然后先给这个vector赋一个空值,然后将数据拷贝进入新建的vector。
将新建的vector数据和老vector数据交换
最后释放新建的vector,老的vector也完成了数据交换
就好比辛亥革命,袁世凯窃取革命的劳动成果基本上没花费什么力气。
第十步:析构函数
没什么需要解释的
~vector()
{
if (_start)
{
delete[]_start;
_start = _finish = _endofstoage = nullptr;
}
}
最后附上总代码
总代码
vector.h:
#pragma once
#include<vector>
#include<string>
#include<iostream>
#include<algorithm>
#include<functional>
#include<assert.h>
using namespace std;
namespace bit
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstoage(nullptr)
{}
~vector()
{
if (_start)
{
delete[]_start;
_start = _finish = _endofstoage = nullptr;
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endofstoage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finfish);
std::swap(_endofstoage, v._endofstorage);
}
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstoage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
this->swap(tmp);
}
vector<T>& operator=(vector<T> v)
{
this->swap(v);
return *this;
}
size_t size()const
{
return _finish - _start;
}
size_t capacity()const
{
return _endofstoage - _start;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* tmp = new T[n];
if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
delete[]_start;
}
_start = tmp;
}
_finish = _start + sz;
_endofstoage = _start + n;
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
void clear()
{
_finish = _start;
}
void pop_back()
{
/*if (_finish > _start)
{
--_finish;
}*/
erase(end()-1);
}
void resize(size_t n,const T& val=T())
{
if (n > capacity())
{
reserve(n);
}
if (n > size())
{
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
else
{
_finish = _start + n;
}
}
void push_back(const T& x)
{
/*if (_finish == _endofstoage)
{
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
*_finish = x;
++_finish;*/
insert(end(), x);
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstoage)
{
size_t n = pos - _start;
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
pos = _start + n;
}
iterator end = _finish-1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _endofstoage;
};
void test_vector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
auto it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
void test_vector2()
{
vector<int> v;
/*v.resize(10, -1);
for (auto e : v)
{
cout << e << " ";
}*/
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.insert(v.begin(), 0);
for (auto e : v)
{
cout << e << " ";
}
}
void test_vector3()
{
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();
auto pos = find(v.begin(), v.end(), 2);
if (pos != v.end())
{
v.erase(pos);
}
for (auto e : v)
{
cout << e << " ";
}
}
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"vector.h"
using namespace std;
int main()
{
bit::test_vector1();
return 0;
}