【C++】vector的模拟实现不会怎么办?看过来

简介: 【C++】vector的模拟实现不会怎么办?看过来

本文章按照逻辑链来完成vector的模拟实现,重点介绍迭代器失效问题,文末赋上源码.h


开始前,翻阅源码得知:


template<class T, class Alloc = alloc>
class vector 
{
public:
    typedef T* iterator;
private:
    iterator start;
    iterator finish;
    iterator end_of_storage;
};


其实类似于我们之前的_a、_size、_capacity ~


0a2653c851af460fa595bd959398a8f1.png


_size = _finish - _start

_capacity = _end_of_storage - _start


一. 构造和析构


无参构造

迭代器区间构造(后面实现)

类似顺序表,初始化都给空,不然push_back就会崩


//无参构造
vector()
  :_start(nullptr)
  , _finish(nullptr)
  , _end_of_storage(nullptr)
{}


🧡析构函数


~vector()
  {
  delete[] _start;
  _start = _finish = _end_of_storage = nullptr;
  }


二. 一系列基本接口


🌈size & capacity


size_t capacity() const
  {
    return _end_of_storage - _start;
  }
  size_t size() const
  {
    return _finish - _start;
  }


🌈[]


_start 原生指针,可以用下标访问


T& operator[](size_t pos)
  {
    assert(pos < size());
    return _start[pos];
  }
  const T& operator[](size_t pos) const
  {
    assert(pos < size());
    return _start[pos];
  }


🌈iterator迭代器


普通迭代器 & const迭代器,老生常谈的问题


iterator begin()
  {
    return _start;
  }
  iterator end()
  {
    return _finish;
  }
  const_iterator begin() const
  {
    return _start;
  }
  const_iterator end() const
  {
    return _finish;
  }


迭代器支持, 范围for同样也能实现


三、尾插尾删


插入数据要考虑扩容问题


reserve & resize

🌊resize

0a2653c851af460fa595bd959398a8f1.png


void resize(size_t n, const T& val = T() )//0初始化语法
  {
    if (n > capacity())
    {
    reserve(n);
    }
    if (n > size())
    {
    //初始化填值
    while (_finish < _start + n)
    {
      *finish = val;
      ++_finish;
    }
    }
    else
    {
    _finish = _start + n;
    }
  }


注意:


匿名对象具有常性,不可更改,所以加const


resize默认填充的是T类型的缺省值,int类型就是0;指针类型就是空指针;若是自定义类型,比如vector,那么就是调用的vector的构造函数,构造vector的匿名对象,是零初始化的语法, x默认值为对应类型的0值


C++为了迎合模板需要,内置类型int等被认为有构造函数

0a2653c851af460fa595bd959398a8f1.png


🌊reserve

同样需要深拷贝,mencpy是浅拷贝


void reserve(size_t n)
    {
        if (n > capacity())
        {
            T* tmp = new T[n];
            size_t sz = size();
            //若原来已有空间,则需要释放旧空间、拷贝数据
            if (_start)
            {
                //memcpy(tmp, _start, sizeof(T)*sz);
                //delete[] _start;
                for (size_t i = 0; i < sz; i++)
                {
                    tmp[i] = _start[i];
                }
                delete[] _start;
            }
            _start = tmp;
            _finish = _start + sz;
            _endofstorage = _start + n;
        }
    }


注意:


更新_finish的时候,要提前把sz保存起来

// 错误示范,请勿模仿
_start = tmp;
_finish = _start + size();
_endofstorage = _start + n;


因为这样的话,size()是_finish - _start(0),况且_start已经更新了,减完有可能就是负数了


0a2653c851af460fa595bd959398a8f1.png


拷贝数据不能简单地memcpy ,这样虽然vector是深拷贝的,比如vector<vector<int>>,其中的vector<int>仍然是浅拷贝的。对于int没有问题,但是对于自定义类型就出问题了。因此我们需要更改 ——


若T是int,一个一个拷贝,没问题;

如果是自定义类型,一个个的拷贝,就要调用深拷贝赋值了

push_back & pop_back

⚡push_back


 

//1、&引用防止深拷贝  2、+const可以右值引用,防止不能引用是常性的临时变量
    void push_back(const T& x)
    {
        if (_finish == _end_of_storage)
        {
            reserve(capacity() == 0 ? 4 :capacity()*2);
        }
        *_finish = x;
        _finish++;
    }


⚡pop_back


void pop_back()
    {
        assert(_finish > _start);
        _finish--;
    }


四、构造 & 拷贝构造 & 赋值重载


💦迭代器区间构造


//写成模板 :可以用其他迭代器区间进行构造 
  template <class InputIterator>
  vector(InputIterator first, InputIterator last)
  :_start(nullptr)
  ,_finish(nullptr)
  ,_end_of_storage(nullptr)
  {
  while(first != last)
  {
    push_back(*first);
    ++first;
  }
  }


💢注意:


一个类模板的成员函数又可以是一个函数模板,这样既可以用自身类型的迭代器,可以用其他任意迭代器区间进行构造 ,只要这个迭代器解引用的数据能和T匹配。

一定要记住初始化,没有初始化就是随机值,出现野指针。扩容时会拷贝数据释放空间,就崩溃了

关于InputIterator的含义:函数模板的模板参数传迭代器区间是有命名规范的

迭代器分为四类,来访问容器——


这个后面会讲解到


💦拷贝构造——多种写法


void swap(const T& v)
  {
  std::swap(_start, v._start);
  std::swap(_finish, v._finish);
  std::swap(_end_of_storage, v._end_of_storage);
  }
  //现代写法2 : v2(v1)   资本家思维
  vector(const vector<T>& v)
  :_start(nullptr)
  , _finish(nullptr)
  , _end_of_storage(nullptr)
  {
  vector<T> tmp(v.begin(), v.end());//打工人
  swap(tmp);//交换
  }


初始化的时候必须置nullptr,不然free的就是一块随机值的空间了


💦赋值重载——现代写法


复用拷贝构造

v是局部变量,出作用域也会析构,相当于你叫外卖小哥帮你丢垃圾

//赋值重载    v1 = v2
  vector<T> operator= (vector<T> v)
  {
    swap(v);//
    return *this;
  }


这里是传值调用,v是v2拷贝构造来的。到这里我都忘了为什么拷贝构造不能传值必须传引用传送门🎉🎉:类和对象,因为拷贝构造要传参,传参完又是拷贝构造,会无限递归


五、迭代器失效问题


🎨insert


🐋检查空间 —— 挪动数据 ——插入数据。 测试代码,发现insert插入第5个数据(即扩容时),出现了随机值


//错误示范
  void insert(iterator pos, const T& x)
  {
    assert(pos >= _start);
    assert(pos <= finish);//=就是尾插
    if (_finish == _end_of_storage)
    {
    reserve(capacity() == 0 ? 4 : capacity() * 2);
    }
    //挪动数据
    iterator end = _finish - 1;
    while (end >= pos)
    {
    *(end + 1) = *(end);
    --end; 
    }
    *pos = x;
    ++_finish;
  }


测试代码 ——


void test_vector2()
  {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  auto p = find(v.begin(), v.end(), 3);
  if (p != v.end())
  {
    v.insert(p, 30);
  }
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  }


出现随机值


0a2653c851af460fa595bd959398a8f1.png


因为有一个隐藏的bug:如果insert时发生了扩容,会导致it指向的空间被释放,此时it还指向着已被释放的空间是野指针,这种问题,我们就叫做迭代器失效


2d65d23f6d4748949b924e4057485923.png


💗于是,我们通过增容后,提前算出相对位置,更新pos来解决。此时外边的p仍然是失效的,则通过传返回值(An iterator that points to the first of the newly inserted elements.),若之后还需要使用p,再根据需要接收返回值(不能传引用,传引用会引发其他问题:返回常量)


🎉最终版:


void insert(iterator pos, const T& x)
  {
    assert(pos >= _start);
    assert(pos <= _finish);//=就是尾插
    if (_finish == _end_of_storage)
    {
       //扩容会导致迭代器失效,需要提前计算更新pos
    size_t len = pos - _start;//提前算出相对位置
    reserve(capacity() == 0 ? 4 : capacity() * 2);
    pos = _start + len;
    }
    //挪动数据
    iterator end = _finish - 1;
    while (end >= pos)
    {
    *(end + 1) = *(end);
    --end; 
    }
    *pos = x;
    ++_finish;
  }


同样测试得:


//测试insert - 迭代器失效问题
  void testVector4()
  {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  vector<int>::iterator it = find(v.begin(), v.end(), 2);
  if (p != v.end())
  {
      // 在it位置插入数据后,不要在访问p,因为p可能失效了
    p = v.insert(it, 20);
    // 若insert时发生了扩容,会导致it指向的空间被释放
    // it本质上是野指针,这种问题,我们就叫做迭代器失效
  }
  v.insert(p, 10);
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  }


🎨erase


常规思路:挪动数据覆盖。此处没有新开辟空间,也就没有野指针数据


// 错误示范,请勿模仿
  void erase(iterator pos);
  {
    assert(pos >= _start);
    assert(pos < _finish);
    iterator begin = pos + 1;
    while (begin < _finish)
    {
    *(begin - 1) = *begin;
    ++begin;
    }
    --finish;
  }


erase缩容也可能到时pos失效(具体看编译器实现)


现在,要求实现删除v中所有偶数,测试代码


//测试erase - 迭代器失效问题2
  void test_vector4()
  {
  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.erase(it);
    }
    ++it;
  }
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  }


通过一系列测试: 发现


1 2 3 4 5 -> 正常(误打误撞)

1 2 4 5   -> 没删干净    

1 2 3 4   -> 崩溃    


以1245为例,偶数相连的情况;删除完2后,++it,导致后一个偶数没判断,跳过了4

再分析1234,当erase最后一个偶数时,++it,此时的it和_finish擦边而过了,*it就不断越界访问了,肯定崩溃

总结,erase(it)后,it指向的位置意义已经变了,直接++,会导致一些意料之外的结果,这也是迭代器失效的问题。


💦我们翻阅文档得知:函数调用后会返回刚删除位置的下一个位置,即这个意义已经改变的pos(An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.)(我网易翻译的应该没错)


💗终极版erase


//stl规定erase返回删除位置的下一个位置
  iterator erase(iterator pos)
  {
    assert(pos >= _start);
    assert(pos < _finish);
    iterator begin = pos + 1;
    while (begin < _finish)
    {
    *(begin - 1) = *begin;
    ++begin;
    }
    --_finish;
    return pos;//此时移动完pos就是被删的后一个数据
  }


继续测试代码~


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


结果正确


0a2653c851af460fa595bd959398a8f1.png


🎨小总结


迭代器失效主要发生在insert和erase中,用了迭代器并改变了底层的数据结构。迭代器失效了,就不要再去访问pos位置;一定要更新,若还需要访问,可先接收返回值更新

💢memcpy拷贝问题

假设模拟实现的vector中的reserve接口中,使用memcpy进行的拷贝,以下代码会发生什么问题?


int main()
{
   bite::vector<bite::string> v;
   v.push_back("1111");
   v.push_back("2222");
   v.push_back("3333");
   return 0; 
 }


对于内置类型可以用mencpy,对于自定义类型就不能用mencpy了,memcpy是浅拷贝,指向同一块空间,要是析构or一个对象修改数据,就出问题了,就需要一个一个对象进行深拷贝赋值。


💢动态二维数组理解


调用函数


0a2653c851af460fa595bd959398a8f1.png


附源码

vector.h

#pragma once
#include<algorithm>
#include <iostream>
#include <vector>
using namespace std;
#include<assert.h>
namespace ljj
{
  template<class T>
  class vector
  {
  public:
  typedef T* iterator;
  typedef const T* const_iterator;
  iterator begin()
  {
    return _start;
  }
  iterator end()
  {
    return _finish;
  }
  const_iterator begin() const
  {
    return _start;
  }
  const_iterator end() const
  {
    return _finish;
  }
  vector()
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
  {}
  //写成模板 :可以用其他迭代器区间进行构造 
  template <class InputIterator>
  vector(InputIterator first, InputIterator last)
    :_start(nullptr)
    ,_finish(nullptr)
    ,_end_of_storage(nullptr)
  {
    while(first != last)
    {
    push_back(*first);
    ++first;
    }
  }
  //拷贝构造 ~ 传统写法
  //v1(v)
  //vector(const vector<int>& v)
  //{
  //  _start = new T[v.size()];
  //  //memcpy(_start, v._start, sizeof(T) * v.size());//有坑
  //  for (size_t i = 0; i < v.size(); ++i)
  //  {
  //  _start[i] = v.start[i];
  //  }
  //  _finish = _start + v.size();
  //  _end_of_storage = _start + v.size();
  //}
  //现代写法1 : v2(v1)
  //vector(const vector<T>& v)
  //  :_start(nullptr)
  //  ,_finish(nullptr)
  //  ,_end_of_storage(nullptr)
  //{
  //  reserve(v.size());
  //  for (const auto& e : v)
  //  {
  //  push_back(e);
  //  }
  //}
  void swap(vector<T>& v)
  {
    std::swap(_start, v._start);
    std::swap(_finish, v._finish);
    std::swap(_end_of_storage, v._end_of_storage);
  }
  //现代写法2 : v2(v1)   资本家思维
  vector(const vector<T> & v)
    : _start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
  {
    vector<T> tmp(v.begin(), v.end());//打工人
    swap(tmp);//交换
  }
  vector(size_t n, const T& val = T())
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
  {
    reserve(n);
    for (size_t i = 0; i < n; ++i)
    {
    push_back(val);
    }
  }
  //赋值重载    v1 = v2
  vector<T> operator= (vector<T> v)
  {
    swap(v);//
    return *this;
  }
  T& front()
  {
    assert(size() > 0);
    return *_start;
  }
  T& back()
  {
    assert(size() > 0);
    return *(finish - 1);
  }
  ~vector()
  {
    delete[] _start;
    _start = _finish = _end_of_storage = nullptr;
  }
  size_t capacity() const
  {
    return _end_of_storage - _start;
  }
  size_t size() const
  {
    return _finish - _start;
  }
  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 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 reserve(size_t n)
  {
    //大于才扩容
    if (n > capacity())
    {
    size_t sz = size();
    T* tmp = new T[n];
    if (_start)
    {
      //memcpy(tmp, _start, sizeof(T) * sz);
      for (size_t i = 0; i < sz; ++i)
      {
      tmp[i] = _start[i];
      }
      delete[] _start;
    }
    _start = tmp;
    _finish = _start + sz;
    _end_of_storage = _start + n;
    }
  }
  void push_back(const T& x)//1、&引用防止深拷贝  2、+const可以右值引用,防止不能引用常性的临时变量
  {
    if (_finish == _end_of_storage)
    {
    reserve(capacity() == 0 ? 4 : capacity() * 2);
    }
    *_finish = x;
    ++_finish;
  } 
  void pop_back()
  {
    assert(_finish > _start);
    _finish--;
  }
  iterator insert(iterator pos, const T& x)
  {
    assert(pos > _start);
    assert(pos <= _finish);//=就是尾插
    if (_finish == _end_of_storage)
    {
    size_t len = pos - _start;//提前算出相对位置
    reserve(capacity() == 0 ? 4 : capacity() * 2);
    pos = _start + len;
    }
    //挪动数据
    iterator end = _finish - 1;
    while (end >= pos)
    {
    *(end + 1) = *(end);
    --end; 
    }
    *pos = x;
    ++_finish;
    return pos;
  }
  //stl规定erase返回删除位置的下一个位置
  iterator erase(iterator pos)
  {
    assert(pos >= _start);
    assert(pos < _finish);
    iterator begin = pos + 1;
    while (begin < _finish)
    {
    *(begin - 1) = *begin;
    ++begin;
    }
    --_finish;
    return pos;
    //if (size() < capacity() / 2)
    //{
    //  //缩容 --以时间换空间
    //}
  }
  private:
  iterator _start;
  iterator _finish;
  iterator _end_of_storage;
  };
  void test_vector1()
  {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  for (size_t i = 0; i < v.size(); ++i)
  {
    cout << v[i] << " ";
  }
  cout << endl;
  //迭代器
  vector<int>::iterator it = v.begin();
  while (it != v.end())
  {
    cout << *it << " ";
    ++it;
  }
  cout << endl;
  for (auto ch : v)//傻瓜式的替换 begin ——》Begin都会报错
  {
    cout << ch << " ";
  }
  cout << endl;
  }
  //测试insert - 迭代器失效问题
  void test_vector2()
  {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.push_back(200);
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  auto p = find(v.begin(), v.end(), 3);
  if (p != v.end())
  {
    v.insert(p, 30);
  }
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  }
  //测试erase - 迭代器失效问题
  void test_vector3()
  {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  auto p = find(v.begin(), v.end(), 3);
  if (p != v.end())
  {
    v.erase(p);
  }
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  }
  //测试erase - 迭代器失效问题2
  void test_vector4()
  {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(4);
  v.push_back(5);
  //要求删除所有的偶数
  auto it = v.begin();
  while (it != v.end())
  {
    if (*it % 2 == 0)
    {
    it = v.erase(it);
    }
    else
    {
    ++it;
    }
  }
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  }
  //测试拷贝构造
  void test_vector5()
  {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(4);
  v.push_back(5);
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  vector<int> v1(v);
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  }
  //迭代器区间构造
  void test_vector6()
  {
  string s("hello world");
  vector<int> v(s.begin(), s.end());
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;
  }
  void test_vector7()
  {
  vector<int*> v1(10);
  for (auto e : v1)
  {
    cout << e << " ";
  }
  cout << endl;
  //int  int 
  //vector<int> v2(10,10);//编译报错:非法的间接寻址  语法问题
  //for (auto e : v2)
  //{
  //  cout << e << " ";
  //}
  //cout << endl;
  // char   int
  vector<char> v3(10, 'a');
  for (auto e : v3)
  {
    cout << e << " ";
  }
  cout << endl;
  }
  //测试resize
  void test_vector8()
  {
  vector<int> v1;
  v1.resize(10, 0);
  for (auto e : v1)
  {
    cout << e << " ";
  }
  cout << endl;
  vector<int> v2;
  v2.reserve(10);
  v2.push_back(1);
  v2.push_back(2);
  v2.push_back(3);
  v2.push_back(4);
  v2.resize(8,8);
  for (auto e : v2)
  {
    cout << e << " ";
  }
  cout << endl;
  v2.resize(20,20);
  for (auto e : v2)
  {
    cout << e << " ";
  }
  cout << endl;
  v2.resize(3);
  for (auto e : v2)
  {
    cout << e << " ";
  }
  cout << endl;
  }
}


test.c


#include"vector.h"
int main()
{
  //ljj::test_vector1();
  //ljj::test_vector2();
  //ljj::test_vector3();
  //ljj::test_vector4();
  //ljj::test_vector5();
  //ljj::test_vector6();
  //ljj::test_vector7();
  ljj::test_vector8();
  return 0;
}


相关文章
|
6天前
|
C++
c++的学习之路:13、vector(2)
c++的学习之路:13、vector(2)
24 0
|
6天前
|
编译器 C++
C++:vector增删查改模拟实现
C++:vector增删查改模拟实现
57 0
|
6天前
|
存储 C++ 容器
【C++】vector的底层剖析以及模拟实现
【C++】vector的底层剖析以及模拟实现
|
6天前
|
编译器 C++ 容器
【C++练级之路】【Lv.7】【STL】vector类的模拟实现
【C++练级之路】【Lv.7】【STL】vector类的模拟实现
|
6天前
|
存储 算法 测试技术
C++:Vector的使用
C++:Vector的使用
|
6天前
|
存储 算法 C++
【C/C++ Vector容量调整】理解C++ Vector:Reserve与Resize的区别与应用
【C/C++ Vector容量调整】理解C++ Vector:Reserve与Resize的区别与应用
74 1
|
6天前
|
编译器 C++ Windows
【C++】vector问题解决(非法的间接寻址,迭代器失效 , memcpy拷贝问题)
不使用memcpy函数不就可以了,然后我们使用简单粗暴的赋值拷贝,这样就不会发生浅拷贝问题了!!!
18 1
|
6天前
|
算法 C++ 容器
【C++/STL】vector(常见接口、模拟实现、迭代器失效)
【C++/STL】vector(常见接口、模拟实现、迭代器失效)
12 0
|
6天前
|
存储 缓存 编译器
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
|
6天前
|
编译器 C++
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析