> 文档中心 > 【C++】vector模拟实现

【C++】vector模拟实现

文章目录

  • 1、前提铺垫
  • 2、构造和析构析构模拟
    • 2.1构造相关
    • 2.2 析构相关的
    • 2.3 赋值运算符重载
    • 2.4 接口测试
      • 2.4.1 内置类型测试
      • 2.4.2 自定义类型测试
  • 3、迭代器相关接口模拟
    • 3.1 接口实现
    • 3.2 代码测试
      • 3.2.1 内置类型测试
      • 3.2.2 自定义类型测
  • 4、容量相关的接口模拟
    • 4.1 接口实现
    • 4.2 代码测试
      • 4.2.1内置类型测试
      • 4.2.2 测试自定义类型
  • 5、访问元素相关的接口模拟
    • 5.1接口实现
    • 5.2接口测试
  • 6、修改相关的接口模拟
    • 6.1 接口模拟
    • 6.2接口测试
  • 7、动态二维数组相关问题图解

1、前提铺垫

SGI版本的vector的底层管理方式与我们先前学习的数据结构部分的顺序表,略有差别。它在底层是通过迭代器(原生态的指针)来管理空间的。具体的定义如下:

namespace gyj{template<class T>class vector{public:typedef T* iterator;private:iterator start;iterator finish;iterator end_of_storage;};}

在这里插入图片描述

2、构造和析构析构模拟

有了上面的前提知识铺垫,接下来我们模拟实现构造析构相关的接口,并完成相应的测试。
对于构造函数,模拟实现以下接口:

2.1构造相关

(1)默认构造函数----vector();
这个比较简单,直接将所有的成员变量置为空即可
【C++】vector模拟实现
(2)构造并使用n个值为value的元素初始化
vector(size_t n,const T& val = T());

vector(size_t n, const T& val = T()):start(new T[n]), finish(start), end_of_storage(start + n){//对申请的空间初始化for (size_t i = 0; i < n; i++){*finish++ = val;}}

注意:第二个参数val是一个缺省参数。对于没有给定确定值的时候,有两种情况:如果T是内置类型,T()的值为0,如果T是自定义类型,T()调用的就是该自定义类型的默认构造函数。因此,对于自定义类型一定要有默认构造函数,否则就会报错!

提问:这样写存在什么问题吗?
带着问题继续往下看~~

(3)区间构造 vector(iterator first,iterator last);
在这里插入图片描述
(4)拷贝构造 vector(const vector& v)
在这里插入图片描述

2.2 析构相关的

在这里插入图片描述

2.3 赋值运算符重载

【C++】vector模拟实现

2.4 接口测试

2.4.1 内置类型测试

🆗,现在我们来对上述模块的代码进行测试,发现编译的时候报了这样的错误:
它告诉我们在区间构造的地方有非法的间接寻址!!
经过分析,我们发现是调用n个值为val的构造函数时发生了错误,具体如下图:
在这里插入图片描述
分析:
模板在使用的时候,首先要对其进行实例化。编译器在编译阶段需要根据我们提供的实例化方式对参数的类型进行推演。 根据推演的结果生成合理的代码,然后来进行调用。
这里之所以会出错,原因如下:
首先,编译器根据用户提供的参数进行推演,本例中推演结果为 (int , int),因此编译器就会在vector中找两个参数类型都为int的构造方法
因为n个值为val的构造函数第一个参数为size_t(unsigned int),因此淘汰掉

接下来,编译器找了一圈发现只有区间构造能够满足这种情况,因此编译器通过改模板生成了两个参数类型都为int的区间构造函数。而区间构造的参数含义是一空空间的首尾地址。此时对两个int类型的数据进行解引用操作,那也就肯定会报错了。

好的,找到该原因后,我们应该如何解决呢?
我们可以再提供一个n 个值为val的构造函数,此时n的类型为int。提供的代码如下:

vector(int n, const T& val = T()):start(new T[n]), finish(start), end_of_storage(start + n){//对申请的空间初始化for (int i = 0; i < n; i++){*finish++ = val;}}

欧克,解决这个问题之后,我们再次测试该代码,发现一切正常:
在这里插入图片描述

2.4.2 自定义类型测试

自定义一个String类进行测试
在这里插入图片描述

3、迭代器相关接口模拟

3.1 接口实现

在这里插入图片描述

3.2 代码测试

3.2.1 内置类型测试

在这里插入图片描述

3.2.2 自定义类型测

结果正常输出:试
注意:自定义类型的流输出运算符需要我们自己提供,具体方式如下:
在这里插入图片描述

4、容量相关的接口模拟

4.1 接口实现

(1)获取容器中有效元素个数----size_t size()const;
(2)获取容量--------size_t capacity()const;
(3)判空------bool empty();
前三个比较简单,这里直接给出实现代码:
在这里插入图片描述
(4)调整容器有效元素个数
void resize(size_t n,const T& val= T());
想要顺利的编写这个方法,就必须清楚地知道resize的特性!

在这里插入图片描述
🆗,根据上图的特性,我们就可以很顺利地将其对应的代码模拟出来
在这里插入图片描述
(5)调整容器的容量大小—void reserve(size_t n);
reserve的功能特性如下:
在这里插入图片描述
根据特性编写代码:

在这里插入图片描述

4.2 代码测试

4.2.1内置类型测试

(1)测试size()、capacity()、empty()
在这里插入图片描述
(2)测试resize()
在这里插入图片描述
(3)测试reserve()
在这里插入图片描述
对于内置类型,我们的代码是正确的

4.2.2 测试自定义类型

(1)测试size()、capacity()、empty()
在这里插入图片描述
正常!
(2)测试resize()
在这里插入图片描述
测试resize的时候,我们发现执行结果出错了。分析得知,是reserve函数出错导致resize函数出错。因此,我们需要解决reserve函数内的错误!

(3)测试reserve()
单独测试reserve发现程序直接崩溃,这显然是reserve有着大问题~
在这里插入图片描述

🆗,找到问题我们就深入函数内部分析解决即可
从我们的代码入手进行分析:

①首先,初始化一个容器v
在这里插入图片描述

②接着调用reserve接口,将容量扩大至7
第一步:申请新空间
在这里插入图片描述
第二步,使用memcpy将旧空间的元素拷贝至新空间

在这里插入图片描述
第三步 释放旧空间
在这里插入图片描述

ok,看到这里我们可以得到一个结论:
如果对象中涉及到资源管理时,一定不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,会引起内存泄露以及程序崩溃。

那么,我们代码的问题也就迎刃而解了~问题就出在memcpy上。那么我们如何解决呢?使用循环逐一拷贝即可!代码如下:

将reserve函数中的memcpy换成下面的代码
在这里插入图片描述

欧克,现在我们再次来测试resize接口和reserve接口
在这里插入图片描述
在这里插入图片描述
到这里,我们容量相关的测试,告一段落~

5、访问元素相关的接口模拟

5.1接口实现

(1)下标访问 T& operato[](size_t index) | const T& operato[](size_t index) const ;
在这里插入图片描述
(2)获取第一个元素 T& front() | const T& front()const;
在这里插入图片描述

(3)获取最后一个元素 T& back() | const T& back()const;
在这里插入图片描述

5.2接口测试

内置类型正常
在这里插入图片描述
自定义类型也🆗
在这里插入图片描述

6、修改相关的接口模拟

6.1 接口模拟

(1)尾插 void push_back(const T& val);
在这里插入图片描述

(2)尾删 void pop_back();
在这里插入图片描述

(3)任意位置插入 iterator insert(iterator pos,const T& val);
在这里插入图片描述

(4)任意位置的删除iterator erase(iterator pos);
在这里插入图片描述

(5)清空元素 void clear();
【C++】vector模拟实现

(6)交换两个容器 void swap(vector& v);
在这里插入图片描述

6.2接口测试

内置类型🆗
在这里插入图片描述
自定义类型:妥妥的
在这里插入图片描述

7、动态二维数组相关问题图解

以杨辉三角为例,理解动态二维数组的申请及使用过程

void Test9(size_t n){//使用vector定义二维数组,//vv中的每一个元素都是一个一维数组vectorgyj::vector<vector<int>>vv(n);//将二维数组每一行中的vector中的元素初始化为1for (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];}}}

分析每一步的过程:
初始化:
在这里插入图片描述
整体赋值:
在这里插入图片描述
更新”中间部分“的值
在这里插入图片描述
点击 vector 获取vector模拟实现的源代码

好的,到这里,vector部分的内容算是圆满结束了!下篇我们来学习带头结点的双向循环链表(list),感觉有所收获的读友们欢迎评论转发~~
我们下篇见~
在这里插入图片描述