C++模拟底层实现vector
文章目录
1.引言
1.本篇将使用命名空间Arthur来模拟实现一下STL库中的容器vector。包括但不仅限于主要功能:push_back()、popback()、resize()、reserve()、erase()等函数。 以上函数都会包含在vector.h头文件中。
2.首先将会介绍基本的框架,再按照各个函数的具体功能、介绍以及每个模块会涉及的问题做好分类目录以供查阅。
3.STL库中具体vector实现的源码以及一些判异常的逻辑较为复杂,本篇只将其中精华的思想以及经典遇到问题的思想呈现出来,具体细节还烦请转阅STL3.0源码中的具体实现!
2.具体实现
2.1 基本框架
#include<assert.h>
namespace Arthur{
template<class T> //采用模板以便于任意类型的使用
class vector{
public:
typedef T* iterator; //将任意类型T 起别名为iterator以供后续方便使用
typedef const T* const_iterator; //const对象调用就用const_iterator
vector(){}
// ... 具体全部函数实现在vector的public中
private:
iterator _start = nullptr; //定义三个指针分别是表示一段数据的开头 一段数据的结尾 和一段数据的容量
iterator _finish = nullptr;
iterattor _end_of_storage =nullptr;
};
}
1.首先把vector函数写在自己Arthur命名空间内,以防跟库中的冲突。
2.类中把任意类型和const对象的任意类型分别命名,以供普通对象和const对象分别使用。
3.在私有成员项给上_start _finish和 _end _ of _storage三个迭代器表示一段数据的开头,结尾和容量三个数据,并且给上缺省的nullptr以表初始值。
2.2 构造析构函数
vector() //普通构造 上面已经给过缺省值其实没必要再给
: _start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
vector(const vector<T>& v){ //拷贝构造函数,注意不能使用memcpy等函数实现浅拷贝,而要实现深拷贝,单独开辟空间
_start = new T[v.capacity()];
for(size_t i = 0;i<v.size();i++)//循环一个个给到新的对象去
_start[i] = v._start[i];
_finish = _start+v.size(); //给完后更新_finish和_end_of_storage
_end_of_storage = _start +v.capacity();
}
//初始化构造,实现功能如 :vector(10,5)会初始化成10个5
vector(size_t n,const T& val= T()) //这里的T()采用调用默认构造生成匿名对象的方法,节省代码量
{
reserve(n);
for(size_t i =0;i<n;i++)
push_back(val);
}
vector(int n,const T& val= T()) //写个一样的把size_t 改成int类型方便后面int使用不报错
{
reserve(n);
for(int i =0;i<n;i++)
push_back(val);
}
~vector(){
delete[] _start;
_finish = _end_of_storage = nullptr;
}
1.这里实现拷贝构造的时候不能使用浅拷贝,否则新旧对象会占用同一块内存单元会导致报错,且不符合拷贝构造本意。
2.实现初始化构造的时候T()是调用的默认构造生成匿名对象的方法,匿名对象的生命周期原本只在改行,可是这里相当于给了匿名对象一个val别名,生命周期就随着val的结束而结束了。
2.3 各类迭代器size()&capacity()
iterator begin{
return _start;
}
iterator end(){
return _finish;
}
const_iterator begin() const { //const版本,方便const对象
return _start;
}
const_iterator end() const{
return _finish;
}
size_t size(){
reutrn _finish-_start;
}
size_t capacity(){
return _end_of_storage - _start;
}
bool empty(){
return _finish == _start;
}
template<class InputIterator> //这里的InputIterator是让vector适配各类的迭代器 如vector<string>时候能够运行
vector(InputIterator first, InputIterator last)
{
while(first != last){
push_back(*first);
++first;
}
}
1.这里的InputIterator是为了让各类迭代器都能够适配vector,因为各个类型的迭代器都支持迭代器解引用,迭代器++等操作,所以采用了一个模板范式的一个操作。
2.4 运算符重载[]
T& operator[](size_t pos){
assert(pos<size());
return _start[pos];
}
const T& operator[](size_t pos){ const //const 版本
assert(pos<size());
return _start[pos];
}
1。这里在重写了[]运算符后返回的时候只需判断一下位置的合法性就直接可以采用[]来返回结果了。
2.5 reserve()&resize()
void reserve(size_t n){
if(n>capacity()){
size_t sz = size(); //这里定义一个sz保存内存变换之前的长度,后面要用
T* temp = new T[n];
if(_start){
for(size_t i = 0;i<sz;i++)
temp[i] = _start[i];
delete[] _start; //全部换过来之后把原空间释放掉
}
}
_start = temp; //更新各个指针
_finish =_start +sz;
_end_of_storage = _start+n;
}
void resize(size_t n,const T& val = T()) //默认构造生成匿名对象
{
if(n<capacity()){
_finish = _start+n;
}
else{
if(n>capacity()){
reserve(n);
while(_finish !=_start+n)
{
_finish = val;
++_finish;
}
}
}
}
1.这里在reserve的时候不要调用例如memset所造成的浅拷贝问题。
2.resize的容量小于自身的容量就相当于缩容,直接改_finish指针即可。
3。n>resize就把尾部给上n个val即可。
2.6 push_back()&pop_back()
void push_back(const T& x) {
if (_finish == _end_of_storage) //检查容量够不够
{
reserve(capacity() == 0 ? 4 : capacity() * 2);//capacity为0的话就给4
}
*_finish = x;
++_finish;
}
void pop_back() {
assert(!empty());
_finish--;
}
- push_back 直接把尾部给上x,pop_back 直接将_finish–即可。
2.7 insert()&erase()
void insert(iterator pos, const T& val) { //把pos到end的位置往后挪 再在pos位置插入val
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage) //检查容量够不够
{
size_t len = pos - _start; //更新pos 采用相对位置的方法,防止扩容内存更新后pos未更新问题。解决迭代器失效问题
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len; // 如果这里只插入四个值再调用insert 就会发生迭代器失效问题
} // 因为到4之后会扩容,扩容后start,finish等都更新到了新的内存地址上,但是我的pos未更新!!
iterator end = _finish - 1;
while (end >= pos) {
*(end + 1) = *end;
end--;
}
*pos = val;
_finish++;
} //insert以后 我们认为pos失效(不能用pos来*(pos)++ 等操作)
iterator erase(iterator pos) {
assert(pos >= _start);
assert(pos < _finish);
iterator start = pos + 1;
while (start != _finish) { //在要删除的位置后一个挪到前一个上,删除之后将size--
*(start - 1) = *start;
++start;
}
--_finish;
return pos;
}//erase之后我们认为迭代器仍然失效 比如利用迭代器删除偶数个元素时,vs下迭代器会报错 但是比如Linux下不会报错
//为了解决这个问题 我们让返回值变成一个迭代器类型 返回一个新的内存地址的pos
1.这里的插入存在迭代器失效的隐患:容量到4之后会扩容,扩容后start,finish等都更新到了新的内存地址上,但是我的pos未更新!!
为了解决这个问题我们需要更新pos位置,方法是采用记住相对位置的距离len,重新开辟空间后再加上len来更新pos即可。
2.在这里我们认为在insert和erase之后迭代器失效了,例如insert后不能用迭代器实现*(pos)++功能,erase后不能利用迭代器删除偶数个元素的功能。
为了解决这些矛盾,我们让返回值变成一个迭代器,返回一个新内存地址的pos即可。
3.全部代码实现
#pragma once
#include<assert.h>
namespace Arthur {
template<class T>
class vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
vector() //构造
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
vector(const vector<T>& v) {
// reserve(v.capacity());
// memcpy(_start, v._start, sizeof(T) * v.size());//memcpy是浅拷贝 按字节拷贝 不i会开辟新空间 memcpy来copy内置类型不会出问题
// _finish = _start + v.size(); //但是自定义和string类等就不能用memcpy
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++) { //新开空间每次循环赋值 就是深拷贝
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
vector(size_t n, const T& val = T())//这里 不能给0 因为T可能是任意类型,给零不合适,所以这里给默认构造生成匿名对象的方法
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{ //一般匿名对象生命周期只在这一行 因为在这行后没人会用了 //但是val是匿名对象的别名了 生命周期就延长了
reserve(n);
for (size_t i = 0; i < n; i++)
push_back(val);
}
vector(int n, const T& val = T())//这里 不能给0 因为T可能是任意类型,给零不合适,所以这里给默认构造生成匿名对象的方法
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{ //一般匿名对象生命周期只在这一行 因为在这行后没人会用了 //但是val是匿名对象的别名了 生命周期就延长了
reserve(n);
for (int i = 0; i < n; i++)
push_back(val);
}
template <class InputIterator> //这里的InputIterator 代表的就是vector适配任意类型的迭代器
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{//所有迭代器区间一定是左闭右开[first,last)
while (first != last) {
push_back(*first);
++first;
}
}
~vector() {
delete[] _start;
_finish = _end_of_storage = nullptr;
}
size_t size() const { //size()函数
return _finish - _start;
}
size_t capacity() const { //capacity()函数
return _end_of_storage - _start;
}
iterator begin() { //迭代器begin()
return _start;
}
iterator end() { //迭代器end()
return _finish;
}
const_iterator begin() const { //迭代器begin()
return _start;
}
const_iterator end() const { //迭代器end()
return _finish;
}
bool empty() {
return _start == _finish;
}
void reserve(size_t n) {//reserve函数
if (n > capacity()) {
size_t sz = size();
T* temp = new T[n];
if (_start) {
//memcpy(temp, _start, sizeof(T)*size());
for (size_t i = 0; i < sz; i++) { //不用memcpy以防造成深拷贝问题
temp[i] = _start[i];
}
delete[] _start; //delete后面加[]代表删除的是一个数组
}
_start = temp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void resize(size_t n, T val = T()) //默认构造
{
if (n < capacity()) {
_finish = _start + n;//相当于删除
}
else {
if (n > capacity()) {
reserve(n);
while (_finish != _start + n) {
_finish = val;
_finish++;
}
}
}
}
void insert(iterator pos, const T& val) { //把pos到end的位置往后挪 再在pos位置插入val
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage) //检查容量够不够
{
size_t len = pos - _start; //更新pos 采用相对位置的方法,防止扩容内存更新后pos未更新问题。解决迭代器失效问题
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len; // 如果这里只插入四个值再调用insert 就会发生迭代器失效问题
} // 因为到4之后会扩容,扩容后start,finish等都更新到了新的内存地址上,但是我的pos未更新!!
iterator end = _finish - 1;
while (end >= pos) {
*(end + 1) = *end;
end--;
}
*pos = val;
_finish++;
} //insert以后 我们认为pos失效(不能用pos来*(pos)++ 等操作)
iterator erase(iterator pos) {
assert(pos >= _start);
assert(pos < _finish);
iterator start = pos + 1;
while (start != _finish) {
*(start - 1) = *start;
++start;
}
--_finish;
return pos;
}//erase之后我们认为迭代器仍然失效 比如利用迭代器删除偶数个元素时,vs下迭代器会报错 但是比如Linux下不会报错
//为了解决这个问题 我们让返回值变成一个迭代器类型 返回一个新的内存地址的pos
void push_back(const T& x) {
if (_finish == _end_of_storage) //检查容量够不够
{
reserve(capacity() == 0 ? 4 : capacity() * 2);//capacity为0的话就给4
}
*_finish = x;
++_finish;
}
T& operator[](size_t pos)
{
assert(pos < size());
//return _start + pos;
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
//return _start + pos;
return _start[pos];
}
void pop_back() {
assert(!empty());
_finish--;
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
4.总结
我们在vector的实现里面解决了几个经典的问题: 迭代器失效问题、深浅拷贝问题。具体通过这次模拟实现我们更清楚的知道了vector具体底层的实现逻辑究竟是什么。