• C++vector的简单模拟实现



    文章目录

    目录

    文章目录

    前言

    一、vector使用时的注意事项

            1.typedef的类型

            2.vector不是string

            3.vector

            4.算法sort

    二、vector的实现

            1.通过源码进行猜测vector的结构

            2.初步vector的构建

    2.1 成员变量

    2.2成员函数

            2.2.1尾插和扩容

              2.2.2operator[]

            2.2.3  迭代器

            2.2.4尾删、指定位置删除和插入

     3指定位置删除和插入和迭代器失效

            3.1insert和迭代器野指针问题

            3.2erase和迭代器失效

    4.拷贝构造

    5.operator=赋值

    6.迭代器区间初始化

    7.n个val初始化和编译器匹配问题

    三、{}的使用

     四、vector的问题


    前言

            vector的使用方法和string很类似,但是string设计的接口太多,而vector则较少所有我们直接开始模拟实现,如果你对vector的使用不太熟悉可以通过它的文档来了解:vector。我们实现vector的简单模板版本。

            由于模板的小问题,我们在使用模板时最好声明和定义在同一个文件


    一、vector使用时的注意事项

            1.typedef的类型

            在看文档时有很多未见过的类型,实际上那是typedef的:

            2.vector不是string

            不能vector和string等价string是专门对待字符串和字符的自定义类型,而vector是char类型的顺序表

            区别在于vector后面要手动加'\0',而string会自动加'\0'.

            3.vector

            vector是string 的顺序表,每个元素都是string,如果使用vector则空间还需自己手动调整,使用string则不用。

            4.算法sort

            C++库函数中提供了一个包含算法的头文件,现在我们要学会使用sort来排序:

            默认是升序。

    1. vector<int> v1 = { 5,6,1,3,4,10 };
    2. for (const auto& e : v1)
    3. {
    4. cout << e << ' ';
    5. }
    6. std::sort(v1.begin(), v1.end());
    7. cout << endl;
    8. for (const auto& e : v1)
    9. {
    10. cout << e << ' ';
    11. }

     

    那么该如何降序?

           可以使用反向迭代器:

    std::sort(v1.rbegin(), v1.rend());

           使用反函数greater<>:

    1. greater<int> gt;
    2. std::sort(v1.begin(), v1.end(),gt);
    3. //std::sort(v1.begin(),v1.end(),greater());//匿名对象

    greater是降序,升序是less.在C++中,我们<为升序,> 为降序,所有greater是降序,less是升序。

    这里了解一下,后面会详细讲解。

    二、vector的实现

            1.通过源码进行猜测vector的结构

            中,我们先浅浅了解一下,具体实现我们使用我们的思路。

            观察源码typedef:

            观察源码的成员变量:

           start是什么,finish是什么?end_of_storage?从名字上看再对比之前的顺序表结构,或许可以大胆猜测:start到finish是一对和size差不多,end_of_storage应该是capacity

            通过观察成员函数来进行猜测:

            如果finish到了end_of_storage说明满了进行扩容。扩容操作是由insert_aux函数来完成的:

            如果满了就进行扩容,大胆猜测扩容操作时进行三段操作:

    1.把position位置之前的数据拷贝到新空间,

    2.把x插入到新的空间的尾部

    3.再把position位置之后的数据拷贝到新的空间。

    这些了解一下。

             关于construc和destroy实际上和内存池有关:

            由于内存池调用new和delete不会进行构造和初始化,所以construc和destroy是定位new的函数,用于对内存池的空间进行构造(这里是拷贝构造)和析构 

            2.初步vector的构建

            使用声明和定义分离来进行模拟实现。在开始我们先不实现构造,使用给缺省值来解决五默认构造的问题。关于为什么给成员变量缺省值就可以进行插入可以查看这一片文章:类和对象(下)初始化列表

    2.1 成员变量

    1. #pragma once
    2. #include
    3. using namespace std;
    4. #include
    5. namespace vet
    6. {
    7. template<class T>
    8. class vector
    9. {
    10. public:
    11. typedef T* iterator;
    12. private:
    13. //T* _a;
    14. //size_t _size;
    15. //size_t _capacity;
    16. //俩者本质上是一样的
    17. iterator _start;
    18. iterator _finish;
    19. iterator _end_of_storage;
    20. };
    21. }

    2.2成员函数

            2.2.1尾插和扩容

            尾插涉及扩容,这我们直接实现capacity和size函数。

    1. vector.h中
    2. void reserve(size_t n)
    3. {
    4. if (n > capacity())
    5. {
    6. T* tmp = new T[n];
    7. if (_start)//如果为空不进行拷贝
    8. {
    9. memcpy(tmp, _start, sizeof(T) * size());
    10. delete _start;
    11. _start = tmp;
    12. }
    13. _finish = _start + size();
    14. _end_of_storage = _start + n;
    15. }
    16. }
    17. size_t capacity()
    18. {
    19. return _end_of_storage - start;
    20. }
    21. size_t size()
    22. {
    23. return _finish - _start;
    24. }
    25. void push_back(const T& x)
    26. {
    27. if (_finish == _end_of_storage)
    28. {
    29. //扩容
    30. size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
    31. reserve(newcapacity);
    32. }
    33. *finish = x;
    34. ++finish;
    35. }
              2.2.2operator[]
    1. T& operator[](size_t pos)
    2. {
    3. assert(pos < size());
    4. return _start[pos];
    5. }

            测试代码:

            运行会发现,程序奔溃

    1. test.cpp中
    2. void test_myvector1()
    3. {
    4. vet::vector<int> v1;
    5. v1.push_back(1);
    6. v1.push_back(2);
    7. v1.push_back(3);
    8. v1.push_back(4);
    9. v1.push_back(5);
    10. for (int i = 0; i < v1.size(); i++)
    11. {
    12. cout << v1[i] << ' ';
    13. }
    14. }

            通过调试发现_finish = x是对空指针操作,实际上错误是在size()计算阶段:

    而size()是通过_finish - _start来计算的:

    _start指向新的空间,而_finish是nullptr,使用空指针减去_start操作错误。size()不是我们想要的size().

    俩种解决办法:

    1. void reserve(size_t n)
    2. {
    3. if (n > capacity())
    4. {
    5. T* tmp = new T[n];
    6. if (_start)
    7. {
    8. memcpy(tmp, _start, sizeof(T) * size());
    9. delete _start;
    10. }
    11. _finish = tmp + size();
    12. _start = tmp;
    13. _end_of_storage = _start + n;
    14. }
    15. }

    这样顺序不好,用户可能看不懂,所以可以记录一下size()然后进行更新:

    1. void reserve(size_t n)
    2. {
    3. size_t oldsize = size();
    4. if (n > capacity())
    5. {
    6. T* tmp = new T[n];
    7. if (_start)
    8. {
    9. memcpy(tmp, _start, sizeof(T) * size());
    10. delete _start;
    11. }
    12. _start = tmp;
    13. _finish = _start + oldsize;
    14. _end_of_storage = _start + n;
    15. }
    16. }

    结果:

            我们这里并未写析构和构造,系统是抽查的机制,动态开辟的一般在delete的时候进行检测,所以我们有时候可能在越界暴露不出来,推荐写上析构:
            再测试有无问题。

    1. ~vector()
    2. {
    3. if (_start)
    4. {
    5. delete[] _start;
    6. _finish = _end_of_storage = nullptr;
    7. }
    8. }

            2.2.3  迭代器

            有了迭代器,那么就可以使用范围for了;

    1. iterator begin()
    2. {
    3. return _start;
    4. }
    5. iterator end()
    6. {
    7. return _finish;
    8. }

    测试代码:

    1. vet::vector<int> v1;
    2. v1.push_back(1);
    3. v1.push_back(2);
    4. v1.push_back(3);
    5. v1.push_back(4);
    6. v1.push_back(5);
    7. vet::vector<int>::iterator it = v1.begin();
    8. while (it != v1.end())
    9. {
    10. cout << *it << ' ';
    11. it++;
    12. }
    13. cout << endl;
    14. for (const auto& e : v1)
    15. {
    16. cout << e << ' ';
    17. }

    结果:

            2.2.4尾删、指定位置删除和插入
    1. //尾删
    2. void pop_back()
    3. {
    4. assert(size() > 0);
    5. --_finish;
    6. }

     3指定位置删除和插入和迭代器失效

      指定位置删除插入:

            要实现指定位置删除或插入就要找到要删除或插入的值

            观察vector 的文档发现,vector没有find,是因为在算法中已经存在find的模板:体现了复用,vector,list都可以用

            3.1insert和迭代器野指针问题

            空间不够扩容,够了挪动数据。

    1. void insert(iterator pos, const T& x)
    2. {
    3. assert(pos >= _start && pos <= _finish);
    4. if (_finish == _end_of_storage)
    5. {
    6. //扩容
    7. size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
    8. reserve(newcapacity);
    9. }
    10. //挪动数据,从后往前挪
    11. iterator end = _finish - 1;
    12. while (end >= pos)
    13. {
    14. *(end + 1) = *end;
    15. --end;
    16. }
    17. *pos = x;
    18. ++_finish;
    19. }

            运行代码:

    1. void test_myvector2()
    2. {
    3. vet::vector<int> v1;
    4. v1.push_back(1);
    5. v1.push_back(2);
    6. v1.push_back(3);
    7. v1.push_back(4);
    8. for (auto e : v1)
    9. {
    10. cout << e << ' ';
    11. }
    12. cout << endl;
    13. v1.insert(v1.begin(), 0);
    14. for (auto e : v1)
    15. {
    16. cout << e << ' ';
    17. }
    18. }

    结果:

    通过运行结果发现,程序崩溃了,这是为什么?

    实际上这里是迭代器失效了,空间满了需要扩容操作,扩容造成了迭代器失效。如果空间足够则可以正常插入

    迭代器失效是指在使用迭代器遍历集合(如数组、列表、字典等)的过程中,对集合进行了修改(添加、删除操作)导致迭代器指向的元素位置发生变化,从而影响迭代器的正确性和结果不可预测的现象

    我们的代码实在扩容的时候发生了迭代器失效。

    扩容操作改变的是_start和_finish以及_end_of_stroage:

    所以这里的迭代器失效本质是野指针问题

    既然pos迭代器失效,那我们就更新pos迭代器。pos要指向新空间的pos位置:
    记录与_start的距离:size_t len = pos - _start;

    1. void insert(iterator pos, const T& x)
    2. {
    3. assert(pos >= _start && pos <= _finish);
    4. if (_finish == _end_of_storage)
    5. {
    6. size_t len = pos - _start;
    7. //扩容
    8. size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
    9. reserve(newcapacity);
    10. pos = _start + len;
    11. }
    12. //挪动数据,从后往前挪
    13. iterator end = _finish - 1;
    14. while (end >= pos)
    15. {
    16. *(end + 1) = *end;
    17. --end;
    18. }
    19. *pos = x;
    20. ++_finish;
    21. }

    此时运行上面的测试代码:
    结果运行正常。

    一般使用insert往往伴随着find,所以我们使用find再进行测试

    1. vet::vector<int> v1;
    2. v1.push_back(1);
    3. v1.push_back(2);
    4. v1.push_back(3);
    5. v1.push_back(4);
    6. for (auto e : v1)
    7. {
    8. cout << e << ' ';
    9. }
    10. cout << endl;
    11. vet::vector<int>::iterator vi = find(v1.begin(),v1.end(),3);
    12. v1.insert(vi, 10);
    13. for (auto e : v1)
    14. {
    15. cout << e << ' ';
    16. }

    这样又有一个问题:发生扩容时insert以后vi个迭代器实参会不会失效

            仔细想想,空间不足时,需要扩容进行空间转移,而vi指向的是否是原来的空间函数中的pos是否会改变vi?

            答案是会失效,因为,vi是实参,而pos是形参,形参的改变不会影响实参的改变。

    1. vet::vector<int>::iterator vi = find(v1.begin(),v1.end(),3);
    2. if(vi != v1.end())
    3. {
    4. v1.insert(vi, 10);
    5. cout << *vi << endl;
    6. }

    由于不知道什么时候扩容,所以一般认为这种情况是迭代器失效。这时候我们建议不要访问和修改vi指向的空间了(即不使用失效的迭代器)。

            如果非要访问插入的位置呢?该怎么办?文档中insert提供了一种方法就是函数的返回值:

    1. iterator insert(iterator pos, const T& x)
    2. {
    3. //代码..
    4. return pos;
    5. }

    一般也不会这么做,所以一般认为失效了。string也有迭代器失效,其他也不例外。也是通过返回值。

            3.2erase和迭代器失效

            erase要将pos位置之后的数据前移:

    1. void erase(iterator pos)
    2. {
    3. assert(pos >= _start && pos < _finish);
    4. iterator it = pos + 1;//将后面的数据前移
    5. while (it != _finish)
    6. {
    7. *(it - 1) = *it;
    8. ++it;
    9. }
    10. --_finish;
    11. }

    测试代码:

    1. void test_myvector03()
    2. {
    3. vet::vector<int> v1;
    4. v1.push_back(1);
    5. v1.push_back(2);
    6. v1.push_back(3);
    7. v1.push_back(4);
    8. for (auto e : v1)
    9. {
    10. cout << e << ' ';
    11. }
    12. cout << endl;
    13. v1.erase(v1.begin());
    14. for (auto e : v1)
    15. {
    16. cout << e << ' ';
    17. }
    18. }

    测试结果:

    erase也有迭代器失效,我们使用vector库里的删除进行测试看看:

    同样也不能访问,而且是断言错误。而使用返回值时则可以使用:

    既然删除只涉及数据移动,那为什么删除也会是迭代器失效呢?

    由于C++并未规定删除是否会缩容,所以删除时不同的平台可能不同:

    是有可能野指针的。

    就算不缩容,那么在删5的时候呢?删除5的时候后面没有数据,如果要访问迭代器会造成越界访问。这里迭代器失效并不是野指针

    所以我们认为erase后,迭代器失效。

    vs下要访问迭代器的话,同样是使用返回值,那我们实现也使用,但是在删除最后应一个有效元素时,不能进行访问:

    1. iterator erase(iterator pos)
    2. {
    3. assert(pos >= _start && pos < _finish);
    4. iterator it = pos + 1;
    5. while (it != _finish)
    6. {
    7. *(it - 1) = *it;
    8. ++it;
    9. }
    10. --_finish;
    11. return pos;
    12. }

    测试代码:

    1. std::vector<int> v1;
    2. v1.push_back(1);
    3. v1.push_back(2);
    4. v1.push_back(3);
    5. v1.push_back(4);
    6. v1.push_back(5);
    7. int x;
    8. cin >> x;
    9. std::vector<int>::iterator it = find(v1.begin(), v1.end(), x);
    10. if (it != v1.end())
    11. {
    12. it = v1.erase(it);
    13. if(it != v1.end())
    14. cout << *it << endl;
    15. }

    如下题,要删除所有偶数,使用失效的迭代器则会保存,所有应该使用返回值:

    1. void test_myvector05()
    2. {
    3. std::vector<int> v1;
    4. v1.push_back(1);
    5. v1.push_back(2);
    6. v1.push_back(3);
    7. v1.push_back(4);
    8. v1.push_back(5);
    9. for (auto e : v1)
    10. {cout << e << ' ';}
    11. //删除所有偶数
    12. std::vector<int>::iterator it = v1.begin();
    13. while (it != v1.end())
    14. {
    15. if (*it % 2 == 0)
    16. it = v1.erase(it);
    17. else
    18. ++it;
    19. }
    20. for (auto e : v1)
    21. {cout << e << ' ';}
    22. }

    而在linux下不使用返回值则可以。但是不同平台不一样,使用最好使用函数返回值更新

    4.拷贝构造

            在不写拷贝构造函数时,编译器会默认生成,该拷贝是浅拷贝。

    1. void test_myvector06()
    2. {
    3. vet::vector<int> v1;
    4. v1.push_back(1);
    5. v1.push_back(2);
    6. v1.push_back(3);
    7. v1.push_back(4);
    8. v1.push_back(5);
    9. //没有写拷贝构造//浅拷贝
    10. vet::vector<int> v2(v1);
    11. for (auto e : v2)
    12. {
    13. cout << e << ' ';
    14. }
    15. }

    结果肯定是报错,因为浅拷贝是值拷贝成员变量值是一样的,此时v1和v2指向同一块空间

    ,在出函数作用域时会调用析构函数,此时v1、v2都会析构,但是指向同一块空间,所以析构了俩次

    所有我们要写拷贝构造:
            像这样写就可以开辟空防止指向同一块空间。

    1. vector(const vector& v)
    2. {
    3. for (auto e : v)
    4. {
    5. push_back(e);
    6. }
    7. }

    但是由于我们没有写默认构造函数:

    由于我们写了一个拷贝构造,编译器不会自动生成构造函数(类和对象中上)了。

    我们可以通过写默认构造函数来解决也可以通过下面这种方法:

    1. vector() = default;
    2. vector(const vector& v)
    3. {
    4. for (auto e : v)
    5. {
    6. push_back(e);
    7. }
    8. }

    第一行代码使用于编译器强制生成默认构造函数

    我们在后面再添加上默认构造函数,这里我们先完成拷贝构造函数,由于拷贝构造我们使用的是尾插的方式,所以每次插入可能涉及很多扩容,所以我们可以提前开好空间:

    1. vector(const vector& v)
    2. {
    3. reserve(v.capacity());
    4. for (auto e : v)
    5. {
    6. push_back(e);
    7. }
    8. }

            运行时发现不行:

    是因为我们的capacity和size,迭代器不是const成员函数,所以我们加上const:

    实现const迭代器:

    1. typedef T* iterator;
    2. typedef const T* const_iterator;
    3. iterator begin()
    4. {return _start;}
    5. iterator end()
    6. {return _finish;}
    7. const_iterator begin() const
    8. {return _start;}
    9. const_iterator end() const
    10. {return _finish;}

    结果:

    5.operator=赋值

            我们使用现代写法:

    1. //v1 = v2
    2. void swap(vector v)
    3. {
    4. std::swap(_start, v._start);
    5. std::swap(_finish,v._finish);
    6. std::swap(_end_of_storage,v._end_of_storage);
    7. }
    8. //现代写法:传值传参,v出函数作用域会调析构,
    9. //但是我们交换了this和v的成员变量,所以析构的是原来的空间而不是v的而是this的
    10. vector& operator=(vector v)
    11. {
    12. swap(v);
    13. return this;
    14. }

    6.迭代器区间初始化

            allocator是内存池,内存池是自己写的空间配置,我们使用new来开空间就好。

            迭代器区间需要俩个迭代器first和last,我们写成函数模板,可以支持任意类型:

    1. //支持任意容器的迭代器初始化
    2. template <class InputIterator>
    3. vector(InputIterator first, InputIterator last)
    4. {
    5. while (first != last)
    6. {
    7. push_back(*first);
    8. ++first;
    9. }
    10. }

    测试代码:

    1. void test_myvector07()
    2. {
    3. vet::vector<int> v1;
    4. v1.push_back(1);
    5. v1.push_back(2);
    6. v1.push_back(3);
    7. v1.push_back(4);
    8. v1.push_back(5);
    9. for (auto e : v1)
    10. {cout << e << ' ';}
    11. cout << endl;
    12. vet::vector<int> v2(v1.begin()+1,v1.end()-1);
    13. for (auto e : v2)
    14. {cout << e << ' ';}
    15. }

    结果:

    7.n个val初始化和编译器匹配问题

            需要考虑缺省值。

    缺省值能否是0?很明显不能,因为T的类型未知,如果是string、vector、list类型,给0肯定会有问题。所以该怎么办?

    缺省参数一般给常量,但自定义类型怎么办,C++中自定义类型可以传T(),即匿名对象(调用默认构造)。内置类型是否可以这样?是可以的,C++对内置类型进行升级,可以进行构造:
    值分别为0、1、0、2。

    测试代码:

    1. void test_myvector08()
    2. {
    3. vet::vector v1(10);
    4. vet::vector v2(10, "xx");
    5. for (auto e : v1)
    6. {
    7. cout << e << ' ';
    8. }
    9. cout << endl;
    10. for (auto e : v2)
    11. {
    12. cout << e << ' ';
    13. }
    14. }

    结果:

    在使用实例化类模板时,如果对它构造n个1会有意想不到的错误

    1. void test_myvector09()
    2. {
    3. vet::vector v1(10, 1);
    4. for (auto e : v1)
    5. {
    6. cout << e << ' ';
    7. }
    8. }

             报错到了这里?

    这里提一下调试技巧:目前我们知道时我们的测试代码上下都没有其他代码,

    1.如果有其他代码,先通过屏蔽其他代码锁定时哪一段代码出来问题。

    2.通过一步步调试进行观察。

    这里实际上时参数的匹配问题,编译器的选择问题

    由于我们传参的都是int,所以模板实例化的也是int int。

    编译器会匹配更好的,参数更匹配的。

    实际上vector(10,1)也会出错:

    解决办法:要使得匹配到正确的函数我们就要给出一个重载的函数:

    这样就可以匹配到合适的函数。vector库内也面临这样的问题。

    8.默认构造函数vector

            默认构造函数用于进行初始化为nullptr。

    1. vector()
    2. :_start(nullptr)
    3. ,_finish(nullptr)
    4. ,_end_of_storage(nullptr)
    5. {}

    保留一个。

    三、{}的使用

            在类和对象(下)中我们提到了多参数和单参数的隐式类型转换。使用到了{},这时C++11的特性:一切皆可用{}进行初始化

    1. class A
    2. {
    3. public:
    4. A(int a)
    5. :_a1(a)
    6. ,_a2(a)
    7. {}
    8. A(int a1, int a2)
    9. :_a1(a1)
    10. , _a2(a2)
    11. {}
    12. private:
    13. int _a1;
    14. int _a2;
    15. };
    16. int main()
    17. {
    18. //单参数隐式类型转换 1-> 构造临时对象A(1) -> 拷贝构造给 a1 => 优化为直接构造
    19. A a1(1);
    20. A a2 = 1;
    21. A a3{ 1 };
    22. A a4 = { 1 };
    23. //多参数隐式类型转换 1,2-> 构造临时对象A(1,2) -> 拷贝构造给 aa1 => 优化为直接构造
    24. A aa1(1, 2);
    25. A aa2 = { 1,2 };
    26. A aa3{ 1,2 };
    27. return 0;
    28. }

    所以在平常使用中可以使用{}.

    再来看下面代码:

    我们使用库中的vector。

    1. void test_vector1()
    2. {
    3. std::vector<int> v1 = { 1,2,3,4,5,6 };
    4. for (auto e : v1)
    5. {
    6. cout << e << ' ';
    7. }
    8. }

    这里是隐式类型转换,但不是转化为vector而是initializer list,然后再进隐式类型转化,是C++11的一种构造:

    initializer list是C++11新增的类型

    它是一个自定义类型:

    il1和il2是等价的。由于{}内是常量数组。内部实际上是俩个指针,分别指向常量数组的开始和末尾

    它也有迭代器和size,所以支持范围for:

    所以在我们自己实现的vector中要使用{1,2,3,4,5}这样的形式我们要支持initializer_list的构造

    1. vector(initializer_listil)
    2. {
    3. reserve(il.size());
    4. //initializer_list支持迭代器
    5. for (auto e : il)
    6. {
    7. push_back(e);
    8. }
    9. }

    结果:

    有了这个特性我们就可以像下面这样:

    1. class A
    2. {
    3. public:
    4. A(int a = 0)
    5. :_a1(a)
    6. ,_a2(a)
    7. {}
    8. A(int a1, int a2)
    9. :_a1(a1)
    10. , _a2(a2)
    11. {}
    12. private:
    13. int _a1;
    14. int _a2;
    15. };
    16. int main()
    17. {

    {}的用法很杂,使用再使用{}进行初始化时,尽量不要写的太奇怪。

     四、vector的问题

            观察下面的程序:

    1. void test_myvector11()
    2. {
    3. vet::vector v1;
    4. v1.push_back("1111111111111111");
    5. v1.push_back("1111111111111111");
    6. v1.push_back("1111111111111111");
    7. v1.push_back("1111111111111111");
    8. for (auto e : v1)
    9. {
    10. cout << e << endl;
    11. }
    12. cout << endl;
    13. }

            插入4个string,结果没有问题:

            而再插入一个string会发生意料之外的结果:

    程序崩溃了。这是为什么?关键就在我们多插入的一次,通过调试观察:

    到这没有什么问题,然后进memcpy,释放就释放旧空间

    释放旧空间:

    走早这里已经知晓了为什么会改变了,释放的空间是我们拷贝的空间,这样的情况就是浅拷贝。

    画图进行分析:

    memcpy对任意类型数据拷贝是浅拷贝。memcpy对数据一个字节一个字节拷贝。在对_start进行释放时,string会调用析构函数,对其中的_str进行释放。

    解决方案:进行深拷贝:

    1. void reserve(size_t n)
    2. {
    3. size_t oldsize = size();
    4. if (n > capacity())
    5. {
    6. T* tmp = new T[n];
    7. if (_start)
    8. {
    9. //memcpy(tmp, _start, sizeof(T) * size());
    10. for (size_t i = 0; i < oldsize; i++)
    11. {
    12. //进行赋值//内置类型进行赋值,自定义类型调用它的赋值操作
    13. tmp[i] = _start[i];
    14. }
    15. delete[] _start;
    16. }
    17. _start = tmp;
    18. _finish = _start + oldsize;
    19. _end_of_storage = _start + n;
    20. }
    21. }

    进行赋值,内置类型进行赋值自定义类型调用它的赋值操作。在这里tmp[i]和_start[i]相当于是

    string对象进行赋值

    五、源代码

    1. vector.h
    2. #pragma once
    3. #include
    4. using namespace std;
    5. #include
    6. #include
    7. namespace vet
    8. {
    9. template<class T>
    10. class vector
    11. {
    12. public:
    13. typedef T* iterator;
    14. typedef const T* const_iterator;
    15. iterator begin()
    16. {return _start;}
    17. iterator end()
    18. {return _finish;}
    19. const_iterator begin() const
    20. {return _start;}
    21. const_iterator end() const
    22. {return _finish;}
    23. vector()
    24. :_start(nullptr)
    25. ,_finish(nullptr)
    26. ,_end_of_storage(nullptr)
    27. {}
    28. //vector() = default;
    29. vector(const vector& v)
    30. {
    31. reserve(v.capacity());
    32. for (const auto e : v)
    33. {
    34. push_back(e);
    35. }
    36. }
    37. //支持任意容器的迭代器初始化
    38. template <class InputIterator>
    39. vector(InputIterator first, InputIterator last)
    40. {
    41. while (first != last)
    42. {
    43. push_back(*first);
    44. ++first;
    45. }
    46. }
    47. vector(size_t n, const T& val = T())
    48. {
    49. reserve(n);
    50. for (size_t i = 0; i < n; i++)
    51. {
    52. push_back(val);
    53. }
    54. }
    55. vector(int n, const T& val = T())
    56. {
    57. reserve(n);
    58. for (size_t i = 0; i < n; i++)
    59. {
    60. push_back(val);
    61. }
    62. }
    63. vector(initializer_listil)
    64. {
    65. reserve(il.size());
    66. //initializer_list支持迭代器
    67. for (auto e : il)
    68. {
    69. push_back(e);
    70. }
    71. }
    72. //v1 = v2
    73. void swap(vector v)
    74. {
    75. std::swap(_start, v._start);
    76. std::swap(_finish,v._finish);
    77. std::swap(_end_of_storage,v._end_of_storage);
    78. }
    79. //现代写法,传值传参,v出函数作用域会调析构,
    80. //但是我们交换了this和v所以析构的是原来的空间而不是v的
    81. vector& operator=(vector v)
    82. {
    83. swap(v);
    84. return this;
    85. }
    86. ~vector()
    87. {
    88. if (_start)
    89. {
    90. delete[] _start;
    91. _finish = _end_of_storage = nullptr;
    92. }
    93. }
    94. void reserve(size_t n)
    95. {
    96. size_t oldsize = size();
    97. if (n > capacity())
    98. {
    99. T* tmp = new T[n];
    100. if (_start)
    101. {
    102. //memcpy(tmp, _start, sizeof(T) * size());
    103. for (size_t i = 0; i < oldsize; i++)
    104. {
    105. //进行赋值//内置类型进行赋值,自定义类型调用它的赋值操作
    106. tmp[i] = _start[i];
    107. }
    108. delete[] _start;
    109. }
    110. _start = tmp;
    111. _finish = _start + oldsize;
    112. _end_of_storage = _start + n;
    113. }
    114. }
    115. size_t capacity() const
    116. {
    117. return _end_of_storage - _start;
    118. }
    119. size_t size() const
    120. {
    121. return _finish - _start;
    122. }
    123. T& operator[](size_t pos)
    124. {
    125. assert(pos < size());
    126. return _start[pos];
    127. }
    128. void push_back(const T& x)
    129. {
    130. if (_finish == _end_of_storage)
    131. {
    132. //扩容
    133. size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
    134. reserve(newcapacity);
    135. }
    136. *_finish = x;
    137. ++_finish;
    138. }
    139. //尾删
    140. void pop_back()
    141. {
    142. assert(size() > 0);
    143. --_finish;
    144. }
    145. iterator insert(iterator pos, const T& x)
    146. {
    147. assert(pos >= _start && pos <= _finish);//等于支持push_back
    148. if (_finish == _end_of_storage)
    149. {
    150. size_t len = pos - _start;
    151. //扩容
    152. size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
    153. reserve(newcapacity);
    154. pos = _start + len;
    155. }
    156. //挪动数据,从后往前挪
    157. iterator end = _finish - 1;
    158. while (end >= pos)
    159. {
    160. *(end + 1) = *end;
    161. --end;
    162. }
    163. *pos = x;
    164. ++_finish;
    165. return pos;
    166. }
    167. iterator erase(iterator pos)
    168. {
    169. assert(pos >= _start && pos < _finish);
    170. iterator it = pos + 1;
    171. while (it != _finish)
    172. {
    173. *(it - 1) = *it;
    174. ++it;
    175. }
    176. --_finish;
    177. return pos;
    178. }
    179. private:
    180. iterator _start = nullptr;
    181. iterator _finish = nullptr;
    182. iterator _end_of_storage = nullptr;
    183. };
    184. }

    如果你有所收获,可以留下你的点赞和关注,谢谢你的观看!


  • 相关阅读:
    浅谈web前端工程师hr面试经典问题20+
    【IPC 通信】信号处理接口 Signal API(4)
    决策树——预剪枝和后剪枝
    Generator异步方案
    python与分形0020 - Stack of Hexagons
    【Orangepi Zero2 全志H616】驱动串口通信
    PLC面向对象编程系列之数据结构(博途Constant类型和Codesys枚举类型)
    RHCE---作业3
    C++符号
    计算机安全学习笔记(IV):基于角色的访问控制 - RBAC
  • 原文地址:https://blog.csdn.net/Jk_Mr/article/details/139045544