• C/C++ vector模拟实现


    模拟实现:

    框架

    1. namespace yx
    2. {
    3. template<class T>
    4. class vector
    5. {
    6. public:
    7. typedef T* iterator;
    8. private:
    9. iterator _start;
    10. iterator _finish;
    11. iterator _end_of_storage;
    12. };
    13. }

    这里我们声明定义不分离

    reverse()

    新开一个空间,拷贝数据,然后释放旧空间

    代码:

    1. void reserve(size_t n)//满了要扩容
    2. {
    3. if (n > capacity())
    4. {
    5. T* tmp = new T[n];//new 开空间 + 初始化
    6. memcpy(tmp,_start,sizeof(T) * size());//一个一个字节的拷贝下来
    7. delete[] _start;
    8. _start = tmp;
    9. }
    10. _finish = _start + size();
    11. _end_of_storage = _start + n;
    12. }

    capacity()

    1. size_t capcaity() const
    2. {
    3. return _end_of_storage - _start;
    4. }

    size()

    1. size_t size() const
    2. {
    3. return _finsh - _start;
    4. }

    operator[]

    俩版本,一个可读可写,一个只读

    1. T& operator[](size_t i)
    2. {
    3. assert(i < size());
    4. return _start[i];
    5. }
    1. const T& operator[](size_t i) const
    2. {
    3. assert(i < size());
    4. return _start[i];
    5. }

    push_back()

    分析

    1. void push_back(const T& x)
    2. {
    3. if (_finish == _end_of_storage)//扩容
    4. {
    5. size_t newcapacity = capacity() == 0 ? 4 : capcaity * 2;
    6. reverse(newcapacity);
    7. }
    8. *_finish = x;
    9. ++_finish;
    10. }

    这时我们来运行一下

    意外的出错了,为什么呢?

    我们来调试一下

    我们发现_finish竟然等于0,

    其实问题出现在size(),size = _finish - _start ,  但这不是正好吗?

    我们来看一个图,start还是不是原来的start,当然不是了,这是start已经被更新了 ,start = tmp,相当于旧的finish 减新的start,size已经不是我们要的哪个size了。

    修改方法

    第一种

    我们先修改一下start的位置

    在reserve中

    1. void reserve(size_t n)//满了要扩容
    2. {
    3. if (n > capacity())
    4. {
    5. T* tmp = new T[n];//new 开空间 + 初始化
    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. }

    测试成功,但这强依赖顺序了,不太好。

    第二种

    oldsize方法提前存储

    1. void reserve(size_t n)//满了要扩容
    2. {
    3. if (n > capacity())
    4. {
    5. size_t oldsize = size();//oldsize提前存储
    6. T* tmp = new T[n];//new 开空间 + 初始化
    7. if (_start)
    8. {
    9. memcpy(tmp, _start, sizeof(T) * size());//一个一个字节的拷贝下来
    10. delete[] _start;
    11. }
    12. _start = tmp;
    13. _finish = tmp + oldsize;
    14. _end_of_storage = _start + n;
    15. }
    16. }

    ~vector()

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

    迭代器iterator

    下面写的只是一种方式

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

    我们来用范围for遍历测试一下

    测试通过

    三种遍历方式

    1. for (size_t i = 0; i < v1.size(); i++)
    2. {
    3. cout << v1[i] << " ";
    4. }cout << endl;
    5. for (auto e : v1)
    6. {
    7. cout << e << " ";
    8. }cout << endl;
    9. vector<int>::iterator it = v1.begin();
    10. while (it != v1.end())
    11. {
    12. cout << *it << " ";
    13. ++it;
    14. }cout << endl;

    const迭代器

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

    pop_back()

    1. pop_back()
    2. {
    3. assert(size() > 0);
    4. --_finish;
    5. }

    insert()

    头插

    代码:

    1. //头插,在x前插入
    2. void insert(iterator pos, const T& x)
    3. {
    4. //检查是否需要扩容
    5. if (_finish == _end_of_storage)
    6. {
    7. size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
    8. reserve(newcapacity);
    9. }
    10. iterator end = _finish - 1;
    11. while (end >= pos)
    12. {
    13. *(end + 1) = *end;
    14. --end;
    15. }
    16. *pos = x;
    17. ++_finish;
    18. }

    我们测试一下

    为什么是个随机值呢?

    insert迭代器失效

    此时_finish = _end_of_storage ,出现了扩容,

    导致出现了迭代器失效,pos变为野指针

    扩容后,start,finish _end_of_storage都去了新空间,旧空间释放了,而pos还在旧空间里,pos为野指针,所以导致了随机值。

    修改方法,算出pos与start的距离

    修改后代码:

    1. void insert(iterator pos, const T& x)
    2. {
    3. //检查是否需要扩容
    4. if (_finish == _end_of_storage)
    5. {
    6. size_t len = pos - _start;
    7. size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
    8. reserve(newcapacity);//扩容完后pos为野指针
    9. pos = _start + len;//pos找到新空间位置
    10. }
    11. iterator end = _finish - 1;
    12. while (end >= pos)
    13. {
    14. *(end + 1) = *end;
    15. --end;
    16. }
    17. *pos = x;
    18. ++_finish;
    19. }

    测试通过

    it会不会失效呢?失效后的迭代器还能访问吗?

    实参传给形参,形参的改变不会影响实参。但如果出现了扩容呢?insert函数内的pos可以解决野指针问题,但it解决不了。我们就不敢访问这个迭代器了。因为出现了野指针。

    我们访问一下迭代器,出现野指针了吧。

    迭代器失效后的建议是不要访问。

    如果我们给pos加个引用呢?

    测试一下

    我们发现begin传不过去了,为什么?

    v1.begin(),begin() 返回一个迭代器,而且是传值返回,c++规定,传值返回返回的不是_start,返回的是其拷贝,生成临时对象,临时对象具有常性,所以非不要不访问。

    erase()

    头删

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

    测试

    erase的迭代器失效

    报错了,为什么?

    erase迭代器失效

    我们界定erase以后,这个it失效了。为什么?

    第一种情况:缩容

    如果删除后的数据小于容量capacity的一半,就开始缩容

    旧的空间释放掉,导致it变为野指针

    第二种情况:越界

    如果我们删除5呢。

    这里出现了越界(非法访问)。迭代器it也失效了

    所以erase it 以后,it就失效。

    在vs中不会缩容,那它如何判断的呢?vs下的iterator是一个很复杂的类型,不是一个原生指针实现的

    我们可以这样理解,erase  it以后,就把it的类型改了,我们再访问的时候就会报错。 

    如何修改呢?

    给其一个返回值

    我们来删除所有偶数

    1. void test6()
    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. //删除所有偶数
    10. std::vector<int>::iterator it = v1.begin();
    11. while (it != v1.end())
    12. {
    13. if (*it % 2 == 0)
    14. {
    15. v1.erase(it);
    16. }
    17. else
    18. {
    19. ++it;
    20. }
    21. }
    22. for (auto e : v1)
    23. {
    24. cout << e << " ";
    25. }cout << endl;
    26. }

    程序对不对呢?

    当然不对,erase it 一次以后,it就失效了,程序报错。调试一下

    删除完2后,it失效,程序报错,如何修改呢?

    拷贝构造

    我们没有写拷贝构造,编译器会默认生成一个拷贝构造。是浅拷贝,完成的是值的拷贝

    1. void test7()
    2. {
    3. 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. vector<int> v2(v1);
    10. for (auto e : v2)
    11. {
    12. cout << e << " ";
    13. }cout << endl;
    14. }

    我们测试一下

    会导致析构两次,报错。

    我们没有写默认构造,拷贝构造也是构造,构造函数的定义是,只要你写了任意构造,编译器默认就不生成

    这里我们强制编译器生成默认构造

    1. //强制编译器生成默认的构造
    2. vector() = default;
    3. //拷贝构造v2(v1),拷贝构造也是构造
    4. vector(const vector& v)
    5. {
    6. for (auto e : v)
    7. {
    8. push_back(e);
    9. }
    10. }

    测试通过

     swap()

    1. void swap(vector& v)
    2. {
    3. std::swap(_start, v._start);
    4. std::swap(_finish, v._finish);
    5. std::swap(_end_of_storage, v._end_of_storage);
    6. }

    operator=

    v1 = v3,直接传值传参v就是v3的拷贝构造,v1就是this,tihs和v交换,this不要的数据给v,v出了作用域会析构,写法对于所有深拷贝都适合

    1. //赋值 v1 = v3,v1之前的空间要释放,v1要和v3有一样大的空间一样的值
    2. vector operator=(vector v)//直接传值传参v就是v3的拷贝,
    3. {
    4. this->swap(v);
    5. return *this;
    6. }

     测试通过

     迭代器区间初始化

    迭代器区间初始化

    类模板的成员函数
    函数模板,支持任意容器的迭代器初始化

    1. template <class InputIterator>
    2. vector(InputIterator first, InputIterator last)
    3. {
    4. while (first != last)
    5. {
    6. push_back(*first);
    7. ++first;
    8. }
    9. }

    迭代器区间初始化可以更好的控制初始化范围。

    那么再这里写函数模板到底是为了什么?

    任意类型的迭代器都能用。

    char和转化为int,类型提升,用的ascii码

    n个value值构造函数

    1. vector(size_t n, const T& val = T())
    2. {
    3. reserve(n);
    4. for (size_t i = 0; i < n; i++)
    5. {
    6. push_back(val);
    7. }
    8. }

    我们看到val的值为T(),这里是匿名对象,

    那val能不能给0呢?答案是不可以的,为什么?

    当T的为int时,当然可以;但当T为string呢?T为vector呢?是不是就不行了。

    当然,当T()匿名对象的T为int的时候是不是就不对了呢?

    这个在C语言中是不对的,但在C++中是正确的。

    1. void test9()
    2. {
    3. int i = 0;
    4. int j(1);
    5. int k = int();
    6. int x = int(2);
    7. }

    C++对内置类型进行了升级。C++的内置类型也有了构造,为了兼容模板。

    测试

    v2初始化为了xxx

    当我们再写一个v3初始化为1时,出现编译错误,为什么?

    由于此时有两个构造,它会选择更适合自己的模板,所以选择了第一个,*first出现了错误。

    如何修改呢?

    第一种

    在10后面加个u,表示无符号整型

    第二种

    重载了一个构造函数,size_t修改为int

    对于初始化{}

    单参数和多参数对象隐式类型转换

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

    单参数和多参数对象隐式类型转换

    *****************

    单参数用括号,多参数必须用花括号{ }

    1. A aa1(1, 1);
    2. A aa2 = { 2,2 };
    3. A aa2{ 2,2 };
    4. const A& aa8 = { 1, 2};
    5. A aa3(1);
    6. A aa4 = 1;
    const A& aa8 = { 1, 2};

    aa8引用的是{1,2}中间产生的临时对象,具有常性

    c++11规定单参数也可以用花括号{}来初始化

    1. A aa5(1);
    2. A aa6 = { 1 };
    3. A aa7{ 1 };

     结果是一样的,但单参数不建议这样写。

    我们来看一下下面代码是不是隐式类型转换

    1. vector<int> v1 = { 1,2,3,4,5,6 };
    2. for (auto e : v1)
    3. {
    4. cout << e << " ";
    5. }cout << endl;

    这里当然不是,上面描述的有单参数的和两个参数,本质上都去调自己的构造去了。

    这里不是隐式类型转换,上面参数固定,这里参数不固定,可以是3个可以是5个等。

    为什么会这样呢?

    因为c++11里支持initializer_list,新增的一个类型,方便初始化 ,

    	vector<int> v1 = { 1,2,3,4,5,6 };

    这里的1 ,2  ,3 ,4 ,5 ,6 的类型为initializer_list

    它的底层其实是俩指针,一个指向开始,一个指向最后一个数据的下一位,所以这里我们可以用范围for遍历。

    vector v2 = {1,2,3,4,5,6} 这里的{1,2,3,4,5,6}类型为initializer_list,生成临时对象,拷贝构造给v2,优化为构造。

    我们在这里还需要写一个initializer_list的构造

    1. vector(initializer_list il)
    2. {
    3. reserve(il.size());
    4. for (auto e : il)
    5. {
    6. push_back(e);
    7. }
    8. }

    测试

    我们看一下下面这个怎某构造

    vector v3 = {  };

    知识点

    我先把结论告诉你:vector<>里面的数据类型是一个自定义类型时,要考虑深拷贝。

    我们看一下代码

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

    测试一下

    没问题

    如果我们再push一次呢

    代码出错了,为什么???

    我们调试一下

    空间不够,需要扩容,然后把数据给给tmp,_start释放。而数据拷贝给新空间的时候是浅拷贝,string没有深拷贝

    memcpy是一个字节一个字节的拷贝,对任意类型拷贝都是浅拷贝。

    tmp指向的还是原来的哪个地址,而_start空间释放了,它的深拷贝没有发生再vector这一层,而是发生在自定义类型存的数据。

    如何解决呢?

    对_str进行深拷贝,但我们不能访问_str里的数据

    我们直接使用赋值来完成工作

    1. void reserve(size_t n)//满了要扩容
    2. {
    3. if (n > capacity())
    4. {
    5. size_t oldsize = size();//oldsize提前存储
    6. T* tmp = new T[n];//new 开空间 + 初始化
    7. //if (_start)
    8. //{
    9. // memcpy(tmp, _start, sizeof(T) * size());//一个一个字节的拷贝下来
    10. // delete[] _start;
    11. //}
    12. if (_start)
    13. {
    14. for (size_t i = 0; i < oldsize; i++)
    15. {
    16. tmp[i] = _start[i];
    17. }
    18. delete[] _start;
    19. }
    20. _start = tmp;
    21. _finish = tmp + oldsize;
    22. _end_of_storage = _start + n;
    23. }
    24. }

    本集完

  • 相关阅读:
    《Mybatis 手撸专栏》第9章:细化XML语句构建器,完善静态SQL解析
    MySQL——Centos7下环境安装
    阿里云服务操作指南-个人购买版
    仿Mac程序坞放大动画
    Writing Tools I Use To Get More Views And Engagement On My Post
    山东济南建筑模板厂家批发之桉木芯建筑模板
    微分中值定理证明和总结
    API接口文档管理系统平台搭建(更新,附系统源码及教程)
    【LeetCode】94. 二叉树的中序遍历 [ 左子树 根结点 右子树 ]
    【MATLAB源码-第67期】基于麻雀搜索算法(SSA)的无人机三维地图路径规划,输出最短路径和适应度曲线。
  • 原文地址:https://blog.csdn.net/2301_77087344/article/details/139310262