C++vector的使用及模拟(2)

简介: C++vector的使用及模拟(2)

4、vector对象修改操作


image.png


  • 使用示例:


void test_vector4()
{
  int a1[] = { 1, 2, 3, 4 };
  vector<int> v1(a1, a1 + sizeof(a1) / sizeof(int));
  vector<int>::iterator it1 = v1.begin();
  while (it1 != v1.end()) {
    cout << *it1 << " ";
    ++it1;
  }
  cout << endl;
  v1.pop_back();
  v1.pop_back();
  it1 = v1.begin();
  while (it1 != v1.end()) {
    cout << *it1 << " ";
    ++it1;
  }
  cout << endl;
  int a2[] = { 1, 2, 3, 4 };
  vector<int> v2(a2, a2 + sizeof(a2) / sizeof(int));
  // 使用find查找3所在位置的iterator
  vector<int>::iterator pos = find(v2.begin(), v2.end(), 3);
  // 在pos位置之前插入30
  v2.insert(pos, 30);
  vector<int>::iterator it2 = v2.begin();
  while (it2 != v2.end()) {
    cout << *it2 << " ";
    ++it2;
  }
  cout << endl;
  pos = find(v2.begin(), v2.end(), 3);
  // 删除pos位置的数据
  v2.erase(pos);
  it2 = v2.begin();
  while (it2 != v2.end()) {
    cout << *it2 << " ";
    ++it2;
  }
  cout << endl;
  int a3[] = { 1, 2, 3, 4 };
  vector<int> v3(a3, a3 + sizeof(a3) / sizeof(int));
  // 通过[]读写第0个位置
  v3[0] = 10;
  cout << v3[0] << endl;
  // 通过[i]的方式遍历vector
  for (size_t i = 0; i < v3.size(); ++i)
    cout << v3[i] << " ";
    cout << endl;
  vector<int> swapv;
  swapv.swap(v3);
  cout << "v data:";
  for (size_t i = 0; i < v3.size(); ++i)
    cout << v3[i] << " ";
  cout << endl;
  cout << "swapv data:";
  for (size_t i = 0; i < swapv.size(); ++i)
    cout << swapv[i] << " ";
  cout << endl;
  // C++11支持的新式范围for遍历
  for (auto x : v3)
    cout << x << " ";
  cout << endl;
}


image.png


5、vector迭代器失效问题


  • 概念:


迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装(比如:vector和string 迭代器就是原生态指针T*)


因此vector迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,或者在迭代器本身指向的意义改变了,不是我们所想的那样


vector迭代器失效操作:

会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等

本质上:使vector发生扩容,原来动态开辟的空间被释放,但是迭代器在扩容后没有更新,再次使用会报错


示例:


#include <iostream>
using namespace std;
#include <vector>
int main()
{
  vector<int> v{ 1,2,3,4,5,6 };
  auto it = v.begin();
  // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
  // v.resize(100, 8);
  // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
  // v.reserve(100);
  // 插入元素期间,可能会引起扩容,而导致原空间被释放
  // v.insert(v.begin(), 0);
  // v.push_back(8);
  // 给vector重新赋值,可能会引起底层容量改变
  v.assign(100, 8);
  /*
  出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
  而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
  空间,而引起代码运行时崩溃。
  解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
  赋值即可。
  */
  while (it != v.end())
  {
    cout << *it << " ";
    ++it;
  }
  cout << endl;
  return 0;
}


image.png


  1. 指定位置元素的删除或者插入操作--erase/insert


本质上:删除或者插入操作后,使原来的迭代器指向的意义发生了改变


  • 示例:


void testdemo1()
{
  int a[] = { 1, 2, 3, 4 };
  vector<int> v(a, a + sizeof(a) / sizeof(int));
  // 使用find查找3所在位置的iterator
  vector<int>::iterator pos = find(v.begin(), v.end(), 3);
  // 删除pos位置的数据,导致pos迭代器失效,指向的意义发生改变(pos位置的内容是4,不是3了)
  v.erase(pos);
  cout << *pos << endl; // 此处是非法访问
  //解决方案:pos = v.erase(pos);//接收返回值,pos指向删除元素的后一个元素
}
void testdemo2()
{
  int a[] = { 1, 2, 3, 4 };
  vector<int> v(a, a + sizeof(a) / sizeof(int));
  vector<int>::iterator pos = find(v.begin(), v.end(), 3);
  v.insert(pos, 6);//插入后pos迭代器失效,指向的意义发生改变(pos位置的内容是6,不是3了)
  //解决方案:pos=v.insert(pos, 6);//接收返回值,pos指向插入的新元素
  cout << *pos << endl;
}


image.png


  • 解释:


  1. erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,但是,迭代器指代的意义发生改变,vs就认为该位置迭代器失效了(对于插入操作也同理)


  1. 迭代器失效解决办法:在使用前,更新迭代器


三、vector剖析及模拟实现


1、vector框架及常用接口展示


  • 基本框架:


image.png


image.png


  • 实现常用接口展示:


template<class T>
class vector
{
public:
    // Vector的迭代器是一个原生指针
    typedef T* iterator;
    typedef const T* const_iterator;
    //普通迭代器和const迭代器
    iterator begin();
    iterator end();
    const_iterator begin()const;
    const_iterator end()const;
    //无参构造函数
    vector();
    //n个value构造函数
    vector(int n, const T& value = T());
    //迭代器构造
    template<class InputIterator>
    vector(InputIterator first, InputIterator last);
    //拷贝构造
    vector(const vector<T>& v);
    //赋值重载
    vector<T>& operator= (vector<T> v);
    //析构
    ~vector();
    // capacity
    size_t size() const;
    size_t capacity() const;
    bool empty() const;
    //开空间
    void reserve(size_t n);
    //开空间+初始化
    void resize(size_t n, const T& value = T());
    //下标重载
    T& operator[](size_t pos);
    const T& operator[](size_t pos)const;
    //尾插
    void push_back(const T& x);
    //尾删
    void pop_back();
    //交换
    void swap(vector<T>& v);
    //插入和删除
    iterator insert(iterator pos, const T& x);
    iterator erase(iterator pos);
private:
    iterator _start; // 指向数据块的开始
    iterator _finish; // 指向有效数据的尾
    iterator _endOfStorage; // 指向存储容量的尾
};


2、vector模拟常用接口具体细节


注:模拟时为了避免与C++本身提供的vector造成命名冲突,我们选择在命名空间里进行实现


  • 实现代码:


namespace cole
{
    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
        vector()
            :_start(nullptr)
            , _finish(nullptr)
            , _endOfStorage(nullptr)
        {}
        vector(int n, const T& value = T())
            :_start(nullptr)
            , _finish(nullptr)
            , _endOfStorage(nullptr)
        {
            //一定要初始化(不初始化,成员变量为随机值,调用相关接口结果会存在问题,导致reserve会出现问题)
            reserve(n);
            for (int i = 0; i < n; i++)
            {
                //对于内置类型进行赋值,对于自定义类型,调用其复制重载函数(如string)
                _start[i] = value;
            }
            _finish = _start + n;
        }
        //模板类中可以存在模板函数
        template<class InputIterator>
        vector(InputIterator first, InputIterator last)
            :_start(nullptr)
            , _finish(nullptr)
            , _endOfStorage(nullptr)
        {
            //复用接口
            reserve(last - first);
            while (first != last)
            {
                push_back(*first);
                first++;
            }
        }
        vector(const vector<T>& v)
            :_start(nullptr)
            , _finish(nullptr)
            , _endOfStorage(nullptr)
        {
            reserve(v.capacity());
            for (size_t i = 0; i < v.size(); i++)
            {
                //对于内置类型进行赋值,对于自定义类型,调用其复制重载函数(如string)
                _start[i] = v._start[i];
            }
            _finish = _start + v.size();
        }
        //现代式写法
        vector<T>& operator= (vector<T> v)
        {
            swap(v);
            return *this;
        }
        ~vector()
        {
            //释放空间以及置空
            if (_start)
            {
                delete[] _start;
            }
            _start = _finish = _endOfStorage = nullptr;
        }
        // capacity
        size_t size() const
        {
            return _finish - _start;
        }
        size_t capacity() const
        {
            return _endOfStorage - _start;
        }
        bool empty() const
        {
            return _start == _finish;
        }
        void reserve(size_t n)
        {
            if (n > capacity())
            {
                //扩容会重新开辟空间,导致成员变量为nullptr,这里记录下必要数据
                size_t sz = size();
                iterator tmp = new T[n];
                if (_start)
                {
                    for (size_t i = 0; i < sz; i++)
                    {
                        //对于内置类型进行赋值,对于自定义类型,调用其复制重载函数(如string)
                        tmp[i] = _start[i];
                    }
                    delete[] _start;
                }
                _start = tmp;
                _finish = _start + sz;
                _endOfStorage = _start + n;
            }
        }
        void resize(size_t n, const T& value = T())
        {
            //分情况操作
            if (n < size())
            {
                _finish = _start + n;
            }
            else
            {
                if (n > capacity())
                {
                    reserve(n);
                }
                while (_finish != _start + n)
                {
                    *_finish = value;
                    ++_finish;
                }
            }
        }
        T& operator[](size_t pos)
        {
            //pos的合理性
            assert(pos < size());
            return _start[pos];
        }
        const T& operator[](size_t pos)const
        {
            //pos的合理性
            assert(pos < size());
            return _start[pos];
        }
        void push_back(const T& x)
        {
            //vector满的情况
            if (_finish == _endOfStorage)
            {
                reserve(capacity() == 0 ? 4 : capacity() * 2);
            }
            *_finish = x;
            _finish++;
        }
        void pop_back()
        {
            //pos的合理性
            assert(size() > 0);
            _finish--;
        }
        //算法中提供的swap涉及多次深拷贝,这里自己实现,变量交换的效率高
        void swap(vector<T>& v)
        {
            ::swap(_start, v._start);
            ::swap(_finish, v._finish);
            ::swap(_endOfStorage, v._endOfStorage);
        }
        iterator insert(iterator pos, const T& x)
        {
            if (_finish == _endOfStorage)
            {
                //扩容会重新开辟空间,这里记录下必要数据
                size_t len = pos - _start;
                reserve(capacity() == 0 ? 4 : capacity() * 2);
                //扩容后pos会失效,更新pos位置
                pos = _start + len;
            }
            iterator end = _finish;
            while (end != pos)
            {
                *end = *(end - 1);
                end--;
            }
            *pos = x;
            _finish++;
            return pos - 1;
        }
        iterator erase(iterator pos)
        {
            iterator cur = pos + 1;
            while (cur != _finish)
            {
                *(cur - 1) = *cur;
                ++cur;
            }
            --_finish;
            return pos;
        }
    private:
        iterator _start; // 指向数据块的开始
        iterator _finish; // 指向有效数据的尾
        iterator _endOfStorage; // 指向存储容量的尾
    };
}


3、使用memcpy拷贝问题


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


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


问题分析:


memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中


如果拷贝的是自定义类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝


image.png

image.png

image.png

image.png


结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃


4、动态二维数组理解


  • 示例:


// 以杨慧三角的前n行为例:假设n为5
void test5(size_t n)
{
  // 使用vector定义二维数组vv,vv中的每个元素都是vector<int>
  cole::vector<cole::vector<int>> vv(n);
  // 将二维数组每一行中的vecotr<int>中的元素全部设置为1
  for (size_t i = 0; i < n; ++i)
    vv[i].resize(i + 1, 1);
  // 给杨慧三角出第一列和对角线的所有元素赋值
  for (int i = 2; i < n; ++i)
  {
    for (int j = 1; j < i; ++j)
    {
      vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
    }
  }
}


cole::vector<cole::vector<int>> vv(n); 


  • 构造一个vv动态二维数组,vv中总共有n个元素,每个元素都是vector类型的,每行没有包含任何元素,如果n为5时如下所示:


94ab768f38f9438ebea10e478d9ddec0.png


vv中元素填充完成之后,如下图所示 :


image.png




相关文章
|
29天前
|
存储 C++ 容器
【C++】vector的底层剖析以及模拟实现
【C++】vector的底层剖析以及模拟实现
|
1月前
|
存储 算法 测试技术
C++:Vector的使用
C++:Vector的使用
|
1月前
|
存储 算法 C++
【C/C++ Vector容量调整】理解C++ Vector:Reserve与Resize的区别与应用
【C/C++ Vector容量调整】理解C++ Vector:Reserve与Resize的区别与应用
53 1
|
3天前
|
存储 缓存 编译器
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
|
3天前
|
编译器 C++
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
|
14天前
|
存储 算法 编译器
【C++初阶】STL详解(三)vector的介绍与使用
【C++初阶】STL详解(三)vector的介绍与使用
34 0
|
14天前
|
存储 编译器 C++
【C++初阶】STL详解(四)vector的模拟实现
【C++初阶】STL详解(四)vector的模拟实现
45 1
|
19天前
|
存储 编译器 C++
【C++初阶】10. vector的使用及模拟实现
【C++初阶】10. vector的使用及模拟实现
49 1
|
1月前
|
存储 编译器 C语言
C++:Vector的模拟实现
C++:Vector的模拟实现
|
1月前
|
存储 网络协议 C++
C++ Vector容器详解:一站式指南,掌握动态数组的高效使用
C++ Vector容器详解:一站式指南,掌握动态数组的高效使用
53 2