【C++】vector的模拟实现

1.vector的常用接口

先查看vector的常用接口,选择比较常用的接口进行模拟。

image-20220709011503922

STL源码的部分定义

template<class T>
class vector {
public:
  typedef T value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type* iterator;
  typedef const value_type* const_iterator;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
protected:
  iterator start;
  iterator finish;
  iterator end_of_storage;
public:
  iterator begin() { return start; }
  const_iterator begin() const { return start; }
  iterator end() { return finish; }
  const_iterator end() const { return finish; }
  reverse_iterator rbegin() { return reverse_iterator(end()); }
  const_reverse_iterator rbegin() const { 
    return const_reverse_iterator(end()); 
  }
  reverse_iterator rend() { return reverse_iterator(begin()); }
  const_reverse_iterator rend() const { 
    return const_reverse_iterator(begin()); 
  }
  size_type size() const { return size_type(end() - begin()); }
  size_type max_size() const { return size_type(-1) / sizeof(T); }
  size_type capacity() const { return size_type(end_of_storage - begin()); }
  bool empty() const { return begin() == end(); }
  reference operator[](size_type n) { return *(begin() + n); }
  const_reference operator[](size_type n) const { return *(begin() + n); }

  vector() : start(0), finish(0), end_of_storage(0) {}
  vector(size_type n, const T& value) { fill_initialize(n, value); }
  vector(int n, const T& value) { fill_initialize(n, value); }
  vector(long n, const T& value) { fill_initialize(n, value); }
  explicit vector(size_type n) { fill_initialize(n, T()); }
}

2.myvector的构建

template<class T>
class myvector
{
  public:
    typedef T* iterator;
    typedef const T* const_iterator;
    /*....
    	类方法定义
    */
  private:
    iterator _start;
    iterator _finish;
    iterator _endofstorage;
};

image-20220709040341664

3.myvector类方法的实现

3.1myvector的构造函数实现
/*
  源码中vector的构造函数
  vector() : start(0), finish(0), end_of_storage(0) {}
*/
//这里,我们将迭代器初始化为nullptr
myvector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr) {}

myvector(int n,const T&x=T())
{
    T* tmp = new T[n];
   _start = _finish = tmp;
   while (n--)
   {
       *_finish = x;
       _finish++;
    }
    _endofstorage = _finish;
}
3.2end()和begin()

分别返回指向第一个元素的迭代器和指向最后一个元素的迭代器;

iterator begin() { return _start; }
const_iterator begin() const { return _start; }
iterator end() { return _finish; }
const_iterator end() const { return _finish; }
3.3size()、capacity()和empty()
/*
  源码中vector的size()和capacity()
  size_type size() const { return size_type(end() - begin()); }
  size_type capacity() const { return size_type(end_of_storage - begin()); }
*/

/*
	1.迭代器相减就是中间元素的个数
	2.保证const类和非const类都可以调用,为了防止权限的放到,需要对this指针进行修饰
*/
size_t size() const {return size_t(end()-begin());} 
size_t capacity() const {return size_t( _endofstorage-begin());}
bool empty() const {return begin()==end();}
3.4[ ]重载
/*
  源码中vector的size()和capacity()
  typedef value_type& reference;
  typedef const value_type& const_reference;
  reference operator[](size_type n) { return *(begin() + n); }
  const_reference operator[](size_type n) const { return *(begin() + n); }
*/

//可能或出现越界的情况,所以对于越界访问可以加上断言或者抛异常
//写法一:
T& operator[](size_t pos){
			assert(pos < size());
			return _start[pos];
}
//防止权限的放大,需要用const修饰this
const T& operator[](size_t pos) const{
			assert(pos < size()&&pos>-1);
			return _start[pos];
}
//写法二:
T& operator[](size_t pos){
    assert(pos<size()&&pos>-1);
    return *(begin()+pos);
}
//防止权限的放大,需要用const修饰this
const T& operator[](size_t pos) const{
    assert(pos<size()&&pos>-1);
    return *(begin()+pos);
}
3.5resize()和reserve()
/*
	STL源码里的resize()和reserve()使用了空间配置器
void reserve(size_type n) {
    if (capacity() < n) {
      const size_type old_size = size();
      iterator tmp = allocate_and_copy(n, start, finish);
      destroy(start, finish);
      deallocate();
      start = tmp;
      finish = tmp + old_size;
      end_of_storage = start + n;
    }
  }
void resize(size_type new_size, const T& x) {
    if (new_size < size()) 
      erase(begin() + new_size, end());
    else
      insert(end(), new_size - size(), x);
  }
*/

//这里我们实现简单的myvector,暂时不使用空间配置器
//可能传入的T类型是自定义类型,最好调用T的构造函数进行初始化
//目前的写法会出现迭代器失效的情况,下面将给出改正
void reserve(size_t n)
{
    if(n>capacity())
    {
        T* tmp=new T[n];
        if(_start)
        {
            memcpy(tmp,begin(),size()*sizeof(T));
            delete[] _start;
        }
      _start=tmp;
      _finish=_start+size();
      _endofstoage = _start + n;
    }
}
void resize(size_t newsize,const T x=T()){
    if(newsize>capacity())
    {
    	resize(newsize);
    }
    if(n>size())
    {
     	//对内容进行赋值
        while(_finish<_start+newsize)
        {
            *finish=x;
            finish++;
        }
    }
    else
    {
        _finish=_start+newsize;
    }
}
3.5.1push_back()
void push_back(const T&x)
{
    //如果空间不够就要开辟空间
    /*
    if(_finish== _endofstorage)
    {
        size_t newcapacity=capacity()==0?2:2*capacity();
        reserve(newcapacity);
    }
    *_finish=x;
    *_finish++;
    */
    insert(end(),x);
}
3.5.2迭代器失效

对于上面的接口,我们调用test()函数进行测试

void test()
{
	myvector<int>r(10);
	r.push_back(1);
	r.push_back(2);
	r.push_back(3);
	for (auto i : r)
	{
		cout << i << " ";
	}
	cout << endl;
}
int main()
{

	test();
	return 0;
}
//结果程序崩溃

image-20220709135447313

很明显发生了越界访问,接下来进行调试找出程序崩溃的原因。

image-20220709140952779

程序崩溃的点出现在reserve()中,而且可以看到_ finish的值没有变化,_ _ finsh= _ start+size()=_ start+_ finish-_ start=_ finish(因为_ start发生了改变而_ finish没有改变,所以_ finish的值没有变化)

image-20220709141742319

这种情况属于迭代器失效的一种;可以先保存一个size避免迭代器失效

void reserve(size_t n)
{
    size_t size=size();
    if(n>capacity())
    {
        T* tmp=new T[n];
        if(_start)
        {
            memcpy(tmp,begin(),size()*sizeof(T));
            delete[] _start;
        }
      _start=tmp;
    }
       _finish=_start+sz;
      _endofstorage = _start + n;
}
3.5.3 pop_back()尾删函数
/*
源码:
void pop_back() {
    --finish;
    destroy(finish);
  }
*/

//判断一下_finish的位置
void pop_back(){
    if(_finish>_start)
    {
        _finish--;
    }
}
3.6insert()和erase()
//先实现简单的insert(),在任意位置插入一个数据
//返回指向新添加元素的迭代器
iterator insert(iterator pos,const T& x)
{
    //断言
    assert(pos>=_start&&pos<=_finish);
    if(_finish==_endofstorage){
        	size_t newcapacity=capacity()==0?4:2*capacity();
        	reserve(newcapacity);
    }
    iterator ed=_finish;
    while(ed>pos)
    {
        *ed=*(ed-1);
        ed--;
    }
    *pos=x;
    _finish++;
    return pos;
}

image-20220709155207648

3.6.1迭代器失效

第一次插入时插入了一个随机值,下面通过调试解决。

image-20220709155739184

可以观察到pos在扩容后还是指向的扩容前的位置,是在原位置插入了数据,所以无法在扩容后的数组中插入数据。

解决方法:记录插入位置到第一个元素的相对距离,然后在扩容后的数组相对距离相等的位置插入数据

iterator insert(iterator pos,const T& x)
{
    //断言
    size_t n=pos-_start;
    assert(pos>=_start&&pos<=_finish);
    if(_finish==_endofstorage){
        	size_t newcapacity=capacity()==0?4:2*capacity();
        	reserve(newcapacity);
            pos=_start+n;
    }
    iterator ed=_finish;
    while(ed>pos)
    {
        *ed=*(ed-1);
        ed--;
    }
    *pos=x;
    _finish++;
    return pos;
}
3.6.2为什么不传引用
iterator insert(iterator&pos,const T& x)

如果传入引用,有些情况就调用不了。比如v.insert(v.begin(),20)传不了,因为insert()是传值返回,传值返回返回的是临时变量,不能传给引用类型的参数。

3.6.3erase()
/*
源码:
  iterator erase(iterator position) {
    if (position + 1 != end())
      copy(position + 1, finish, position);
    --finish;
    destroy(finish);
    return position;
  }
*/
iterator erase(iterator pos)
{
    assert(pos>=_start&&pos<_finish);
    iterator bg=pos;
    while(bg<_finish-1)
    {
        *bg=*(bg+1);
        bg++;
    }
    _finish--;
    return pos;
}
3.6.4erase()中的迭代器失效
(1).windows环境下vs编译器强制检查导致erase()失效

vs编译器中强制检查pos是否失效;

void test1() {
	vector<int>m;
	m.push_back(1);
	m.push_back(2);
	m.push_back(3);
	m.push_back(4);
	vector<int>::iterator pos = find(m.begin(), m.end(), 2);
	m.erase(pos);
	cout << *pos << endl;
}
int main()
{
    test1();
    return 0;
}

image-20220710210925960

按道理删除操作结束后的pos指向应该为3:image-20220710210055386

但是对pos解引用访对应的元素,发生了权限冲突。

image-20220710211015955

执行erase(pos)以后pos失效,pos的意义变了,原本指向的是2现在指向的是3。vs编译器对pos的访问权限进行了限制。再进行访问时就会报权限冲突。

对于这种情况,不同的编译器处理方式不同,下面是linux环境下的运行结果:image-20220710221837301

**整体总结:**对于insert和erase造成迭代器失效问题,linux平台检查依靠操作系统自身的野指针检查机制;而windows下检查更加严格。

(2)删除所有的偶数
void test_vetcor()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    v.push_back(6);
    auto it=v.begin();
    while(it!=v.end())
    {
        if(*it%2==0)
        {
            v.erase(it);
        }
        it++;
    }
    for(auto e:v)
    {
        cout<<e<<" ";
    }
    cout<<endl;
}

Segmentation fault;结果发生段错误。image-20220710224019097

最后it会越过end(),循环一直进行。

//正确的写法
void test_vetcor()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    v.push_back(6);
    auto it=v.beign();
    while(it!=v.end())
    {
        if(*it%2==0){
            it=v.erase(it);
        }
        else
        {
            it++;
        }
    }
    for(auto e:v)
    {
        cout<<e<<" ";
    }
    cout<<endl;
}

输出结果为1 3 5

3.6.5pop_back()
//可以直接赋用erase()
void pop_back()
{
    /*
    end()返回是一个临时变量,临时变量具有常性,因此不能使用--;
    erase(end()--);
    */
    erase(this->end()-1);
}
3.7析构函数
~myvector()
{
    if(_start){
        delete[] _start;
        _start=_finish=_endofstoage=nullptr;
    }
}
3.8myvector的其他重载的构造函数
//用迭代器构造
template <class InputIterator>
myvector(InputIterator first,InputIterator last){
    while(first!=last){
        push_back(*first);
        first++;
    }
}
3.9myvector的拷贝构造
void Swap(myvector<T>&v)
{
    std::swap(_start,v._start);
    std::swap(_finish,v._finish);
    std::swqp(_endofstoage,v._endofstoge);
}
myvector(const myvector<T>&v)
{
    myvector<T>tmp(v.begin(),v.end());
    Swap(tmp);
}
//可以连续赋值所以需要返回值为引用传参
//传递变量的拷贝,拷贝是临时变量
//直接传递临时变量,生命周期结束自动销毁;
myvector<T>& operator=(myvector<T> V)
{
    Swap(v);
    return *this;
}
myvector(size_t n,const  T& val=T())
    :_start(nullptr),
	_finish(nullptr),
	_endofstorage(nullptr)
{
    reserve(n);
    for(int i=0;i<n;i++)
    {
        push_back(val);
    }
}
3.10myvector构造函数的参数匹配问题
void test()
{
    //目的:构造10个int类型,值为2
    myvector<int>m(10,2);
}
int main()
{
    test();
    return 0;
}

image-20220710235343383

报错:非法间接寻址。

/*
进行调试,可以发现myvector<int>m(10,2)调用的不是我们预想调用的myvector(size_t n,const  T& val=T()),
而是调用了迭代器参数的构造函数。
template <class InputIterator>
myvector(InputIterator first,InputIterator last)
*/

image-20220710235730960

参数匹配问题

程序运行时,编译器会调用最匹配和最合适的函数

template
myvector(InputIterator first,InputIterator last)两个参数相同,而myvector(10,2)编译器会认为是两个int类型的参数,所以编译器会调用迭代器类型的构造函数。

解决方法
//将size_t类型转换为int类型
myvector(int n,const  T& val=T())
    :_start(nullptr),
	_finish(nullptr),
	_endofstorage(nullptr)
{
    reserve(n);
    for(int i=0;i<n;i++)
    {
        push_back(val);
    }
}

4.深层次的深浅拷贝问题

以拷贝杨辉三角形为例

//杨辉三角构造类
class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>>ve;
        //开空间
        ve.resize(numRows);
        for (int i = 0;i < numRows;i++) {
            ve[i].resize(i + 1);
            ve[i][0] = 1;
            ve[i][i] = 1;
        }
        for (int i = 1;i < numRows;i++) {
            for (int j = 1;j < ve[i].size();j++) {
                //记忆化搜索
                if (ve[i][j] == 0) {
                    ve[i][j] = ve[i - 1][j] + ve[i - 1][j - 1];
                }
            }
        }
        return ve;
    }
};

拷贝

void copy()
{
	//先调用Solution的构造函数,然后访问成员函数
	myvector<myvector<int>>vv = Solution().generate(5);
	for (int i = 0;i < vv.size();i++)
	{
		for (int j = 0;j < vv[i].size();j++)
		{
			cout << vv[i][j] << " ";
		}
		cout << endl;
	}
}
int main()
{
	copy();
	return 0;
}

image-20220711105309762

在析构函数的位置发生了权限冲突。

image-20220711111256394

当进行到return ve时,会调用拷贝构造image-20220711120821951

4.1剖析原因
4.1.1vector存储模型

image-20220711221010237

vector中存放了三个成员变量,_ start指针、_ finish指针和_endofstorage指针。

4.1.2函数赋用关系

image-20220711143757374

当容量小于等于4时,myvector<myvector>不会发生扩容。当插入第5行的内容时,就需要调用reserve()扩容。而memcpy()属于浅拷贝,只会拷贝指向前四行的指针。tmp前四个元素和原_start前四个元素指向同一块空间

image-20220711150635468

下一步_start指向的空间被delete释放,调用析构函数。tmp前四个元素指向的空间也被释放。然后insert()第5行。

image-20220711152428666

因此我们可以看到打印的结果是,前四行是随机值,第5行是正确的值。

image-20220711133023296

4.2解决方式

我们需要对浅拷贝的部分进行深拷贝,将memcpy部分进行一一拷贝。

void reserve(size_t n)
{
    size_t sz=size();
    if(n>capacity())
    {
        T* tmp=new T[n];
        if(_start)
        {
            for(size_t i=0;i<size();i++)
            {
                tmp[i]=_start[i];
            }
            delete[] _start;
        }
      _start=tmp;
      _finish=_start+size();
      _endofstoage = _start + n;
    }
}

image-20220711153652504

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

影中人lx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值