大家好久不见,今天我们一起来模拟实现一下STL中的vector容器。
因为物理空间连续,所以我们选择直接将原生指针作为迭代器,且整个vector容器的容量(capacity)和大小(size)均是采用三个迭代器相减来表示,有关迭代器前开后闭,我在string的模拟实现中已经讲解,不理解的可以回去看看。
namespace teacher
{templateclass vector{public://原生指针作为迭代器typedef T* iterator;typedef const T* const_iterator;//容量和大小int capacity() const{return _end_of_storage - _start;}int size() const{return _finish - _start;}//迭代器iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}
不管是构造函数还是拷贝构造,都是要把元素一个一个的赋值上去,因此先写一个push_back函数,在其他构造中调用即可。
另外c++允许在一个模板里的函数使用模板。
需要说明的是,匿名对象的生命周期只在那一行,但如果是const T& x 修饰,生命周期就会延长到x结束为止。
vector() {};
vector(size_t n, const T& val = T())
{reserve(n);for (int i = 0; i < n; i++){push_back(val);}
}
vector(int n, const T& val = T())
{reserve(n);for (int i = 0; i < n; i++){push_back(val);}
}template
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}vector(const vector& v)
{vector tmp(v.begin(),v.end());swap(tmp);
}~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}void push_back(const T& x)
{if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
当我们的vector内存有指向其他位置的指针的时候,简单的字节拷贝(值拷贝
)会让两个vector指向同一个位置。这个问题非常严重,因为如果指向同一位置,意味着要析构多次,这会让我们的程序崩溃,因此我们首先来解决一下深浅拷贝的问题。
如下图所示的扩容逻辑,可以看到我们为vector的每一个元素都重新开辟了空间,这样可以避免浅拷贝的问题。
void reserve(size_t n)
{size_t sz = size();// 容量不够才进行扩容if (n > capacity()){T* tmp = new T[n];//拷贝原来的内容if (_start){for (int i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;
}
但这样写也会有一个很严重的问题,那就是 tmp[i] = _start[i]
这一步其实也是一个浅拷贝,即会出现如图所示的问题:
为了解决这个问题,我们需要手写一个深拷贝的 赋值重载
。
void swap(vector& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}vector operator=(vector v)
{ //v是临时对象,传过来完成了拷贝构造,走了push back的逻辑,因此再交换是开了新空间的。swap(v);return *this;
}
因为我们习惯了数组的[]读取数据,并且vector的内存连续,所以我们重载一下[]
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}
逻辑与string的模拟实现基本一样,因为这里是迭代器也不用考虑npos的问题。
尽管这里都返回了迭代器,但使用这两个函数后,我都建议你不要再使用pos,认为pos已经失效!
iterator insert(iterator pos, const T& val)
{if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 20);pos = _start + len;//扩容后更新pos 防止pos失效}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}iterator erase()
{iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;
}
这三个函数较为简单,一笔带过
bool empty()
{return _start == _finish;
}
void pop_back()
{assert(!empty());--_finish;
}
void resize(size_t n,T val = T())
{if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish != _start + n){*_finish = val;++_finish;}}
}
以上就是本篇文章全部内容了,希望对你有所收获,我们下次再见