C++ 初阶vecotr底层框架模拟实现
Posted SuchABigBug
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 初阶vecotr底层框架模拟实现相关的知识,希望对你有一定的参考价值。
目录
一、前言
vector源代码看了一下过于复杂,这里取其精华把大体的框架和重要函数罗列出来并分析实现
二、vecotr框架搭建
在实现vector前需要弄明白这幅图,与string相似也会记录size和capacity的大小
在这个数组中,有三个指针分别指向起始位置start,数据结束位置finish以及空间容量的末尾end_of_storage位置,剩余空间备用,如果增加的数据超过capacity,则需要进行增容
1. vector介绍
1. vector是大小可变的数组序列容器
2. 和数组一样,空间上是连续存储
3. 使用动态分配数组存储元素;增容时,并不会每次都重新分配大小,而是分配一些额外的空间以适应可能的增长(finish和endofstorage之间)。
vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
4. 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。
对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好。
2. 框架
下面为vector的大体框架,和顺序表也很类似实现了增删查改
namespace vectorSimu{
template<class T>
class vector{
public:
typedef T* iterator;
iterator begin();
iterator end();
//默认成员函数
vector();
vecotr(size_t n, const T& val);
vector(const vector<T>& val); //拷贝构造
//大小和rongliang
size_t size();
size_t capacity();
//扩容
void reserve(size_t n);
void resize(size_t n, const T& val = T());
//读取容器信息
T& operator[](size_t pos);
//修改与删除
void push_back(const T& val);
void pop_back();
void insert(iterator pos, const T& val);
iterator erase(iterator pos);
void swap(vector<T>& v);
//析构
~vector();
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
3. 构造函数
构造一个不带参的函数,将其成员置为nullptr
vector()
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
带参构造,调用可支持初始为T类型的n个这样的val,先去调用reserve以此判断n是否超出当前的容量,如果超出则重新分配一组n个大小的空间
vector(size_t n, const T& val = T())
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
reserve(n);
//判断n是否超出capacity
reserve(n);
for(int i=0; i<n; ++i){
push_back(val);
}
}
我们也可以利用区间迭代器进行构造,将数据尾插
//类模版到成员函数,还可以再定义模版参数
template <class InputIterator>
vector(InputIterator first, InputIterator last){
while(first != last){
push_back(*first);
++first;
}
}
4. 拷贝构造
传统写法方式一:
vector(const vector<T>& v){
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T)*v.size());
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
传统写法方式二:
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(v.capacity());
for(const auto& e: v){
push_back(e);
}
}
深拷贝的现代写法,和上面的传统写法有所不同,传统写法是直接开一组新空间,然后将原来存储的数据放入新空间,而现代写法是通过拷贝构造给临时变量后进行交换
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
this->swap(tmp);
}
void swap(vector<T>& v){
::swap(_start, v._start);
::swap(_finish, v._finish);
::swap(_endofstorage, v._endofstorage);
}
5. 赋值重载
传统写法,此方法和拷贝构造的效果是一样的
vector<T>& operator=(const vector<T>& v){
if(this != &v){
delete [] _start;
_start = _finish = _endofstorage = nullptr;
reserve(v.capacity());
for(const auto& e:v){
push_back(e);
}
}
return *this;
}
现代写法,利用形参进行了一次拷贝,然后this类和拷贝的类进行交换
vector<T>& operator=(vector<T> v){
swap(v);
return *this;
}
6. 迭代器函数
其实迭代器的begin就是封装了此类型的首地址,end对应类型的末尾
typedef T* iterator;
iterator begin(){
return _start;
}
iterator end(){
return _finish;
}
7. size和capacity
这个也比较简单,把地址做一下运算就可以得出来
size_t size(){
return _finish - _start;
}
//总的容量大小
size_t capacity(){
return _endofstorage - _start;
}
8. 扩容
当我们pushback信息到vector时需要判断需不需要增容,如果大于当前容器,那么新开一组空间,将原空间释放即可
void reserve(size_t n){
if(n>capacity()){
size_t sz = size();
T* tmp = new T[n];
if(_start){ //如果开头不为空
for(size_t i=0; i<sz; ++i){
tmp[i] = _start[i];
}
delete[] _start; //释放原容器数据
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
重新分配大小需要考虑三种情况
第一种情况当重新分配的大小小于size,也就是首地址到N的位置,剩余的进行裁剪
第二种情况,当N大于当前容量需要重新reserve()新的空间,并初始化为给定的val值
第三种情况,当N小于当前容量又比size大,剩余的补为给定的val值
void resize(size_t n, const T& val = T()){
if(n < size()){
_finish = _start + n;
}else{
if(n > capacity()){
reserve(n);
}
while(_finish < n + _start){
*_finish = val;
++_finish;
}
}
}
9. pushback和popback
这里需要注意,首次push需要判断finish指针是否和容量指针在同一个位置,那么就需要进行扩容了
//修改与删除
void push_back(const T& val){
if(_finish == _endofstorage){
size_t newcapacity = capacity()==0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = val;
_finish++;
}
pop数据也比较简单,直接将finish指针向前移动
void pop_back()
{
assert(!empty());
--_finish;
}
10. 插入和删除指定位置数据
插入前需要判断pos是否在start和finish之间,否则报错
插入数据前需要判断容量是否足够
在中间插入数据时,pos后面的数据都需要依次向后挪动,此算法效率不高,所以不推荐用
iterator insert(iterator pos, const T& val){
assert(pos >= _start && pos<=_finish);
if(_finish == _endofstorage){
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4: capacity()*2;
reserve(newcapacity);
pos = _start + len;
}
iterator end = _finish - 1;
while(end >= pos){
*(end+1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
将pos以后的数据都去掉
iterator erase(iterator pos){
assert(pos>=_start && pos < _finish);
iterator it = pos + 1;
while(it != _finish){
*(it-1) = *it;
++it;
}
--_finish;
return pos;
}
11. 通过下标访问
//读取容器信息
T& operator[](size_t pos){
assert(pos < size());
return _start[pos];
}
Test进行验证
三、完整代码
Gitee链接🔗 🔗 🔗
👉 👉 👉 vector simulation 👈 👈 👈
创作不易,如果文章对你帮助的话,点赞三连哦:)
以上是关于C++ 初阶vecotr底层框架模拟实现的主要内容,如果未能解决你的问题,请参考以下文章