【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;
	};
}

【C++】vector模拟实现_第1张图片

2、构造和析构析构模拟

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

2.1构造相关

(1)默认构造函数----vector();
这个比较简单,直接将所有的成员变量置为空即可
【C++】vector模拟实现_第2张图片
(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);
【C++】vector模拟实现_第3张图片
(4)拷贝构造 vector(const vector& v)
【C++】vector模拟实现_第4张图片

2.2 析构相关的

【C++】vector模拟实现_第5张图片

2.3 赋值运算符重载

【C++】vector模拟实现_第6张图片

2.4 接口测试

2.4.1 内置类型测试

,现在我们来对上述模块的代码进行测试,发现编译的时候报了这样的错误:
它告诉我们在区间构造的地方有非法的间接寻址!!
经过分析,我们发现是调用n个值为val的构造函数时发生了错误,具体如下图:
【C++】vector模拟实现_第7张图片
分析:
模板在使用的时候,首先要对其进行实例化。编译器在编译阶段需要根据我们提供的实例化方式对参数的类型进行推演。 根据推演的结果生成合理的代码,然后来进行调用。
这里之所以会出错,原因如下:
首先,编译器根据用户提供的参数进行推演,本例中推演结果为 (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;
	}
}

欧克,解决这个问题之后,我们再次测试该代码,发现一切正常:
【C++】vector模拟实现_第8张图片

2.4.2 自定义类型测试

自定义一个String类进行测试
【C++】vector模拟实现_第9张图片

3、迭代器相关接口模拟

3.1 接口实现

【C++】vector模拟实现_第10张图片

3.2 代码测试

3.2.1 内置类型测试

【C++】vector模拟实现_第11张图片

3.2.2 自定义类型测

结果正常输出:【C++】vector模拟实现_第12张图片
注意:自定义类型的流输出运算符需要我们自己提供,具体方式如下:
【C++】vector模拟实现_第13张图片

4、容量相关的接口模拟

4.1 接口实现

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

【C++】vector模拟实现_第15张图片
,根据上图的特性,我们就可以很顺利地将其对应的代码模拟出来
【C++】vector模拟实现_第16张图片
(5)调整容器的容量大小—void reserve(size_t n);
reserve的功能特性如下:
【C++】vector模拟实现_第17张图片
根据特性编写代码:

【C++】vector模拟实现_第18张图片

4.2 代码测试

4.2.1内置类型测试

(1)测试size()、capacity()、empty()
【C++】vector模拟实现_第19张图片
(2)测试resize()
【C++】vector模拟实现_第20张图片
(3)测试reserve()
【C++】vector模拟实现_第21张图片
对于内置类型,我们的代码是正确的

4.2.2 测试自定义类型

(1)测试size()、capacity()、empty()
【C++】vector模拟实现_第22张图片
正常!
(2)测试resize()
【C++】vector模拟实现_第23张图片
测试resize的时候,我们发现执行结果出错了。分析得知,是reserve函数出错导致resize函数出错。因此,我们需要解决reserve函数内的错误!

(3)测试reserve()
单独测试reserve发现程序直接崩溃,这显然是reserve有着大问题~
【C++】vector模拟实现_第24张图片

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

①首先,初始化一个容器v
【C++】vector模拟实现_第25张图片

②接着调用reserve接口,将容量扩大至7
第一步:申请新空间
【C++】vector模拟实现_第26张图片
第二步,使用memcpy将旧空间的元素拷贝至新空间

【C++】vector模拟实现_第27张图片
第三步 释放旧空间
【C++】vector模拟实现_第28张图片

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

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

将reserve函数中的memcpy换成下面的代码
【C++】vector模拟实现_第29张图片

欧克,现在我们再次来测试resize接口和reserve接口
【C++】vector模拟实现_第30张图片
【C++】vector模拟实现_第31张图片
到这里,我们容量相关的测试,告一段落~

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

5.1接口实现

(1)下标访问 T& operato[](size_t index) | const T& operato[](size_t index) const ;
【C++】vector模拟实现_第32张图片
(2)获取第一个元素 T& front() | const T& front()const;
【C++】vector模拟实现_第33张图片

(3)获取最后一个元素 T& back() | const T& back()const;
【C++】vector模拟实现_第34张图片

5.2接口测试

内置类型正常
【C++】vector模拟实现_第35张图片
自定义类型也
【C++】vector模拟实现_第36张图片

6、修改相关的接口模拟

6.1 接口模拟

(1)尾插 void push_back(const T& val);
【C++】vector模拟实现_第37张图片

(2)尾删 void pop_back();
【C++】vector模拟实现_第38张图片

(3)任意位置插入 iterator insert(iterator pos,const T& val);
【C++】vector模拟实现_第39张图片

(4)任意位置的删除iterator erase(iterator pos);
【C++】vector模拟实现_第40张图片

(5)清空元素 void clear();
【C++】vector模拟实现_第41张图片

(6)交换两个容器 void swap(vector& v);
【C++】vector模拟实现_第42张图片

6.2接口测试

内置类型
【C++】vector模拟实现_第43张图片
自定义类型:妥妥的
【C++】vector模拟实现_第44张图片

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

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

void Test9(size_t n)
{
	//使用vector定义二维数组,
	//vv中的每一个元素都是一个一维数组vector
	gyj::vector<vector<int>>vv(n);

	//将二维数组每一行中的vector中的元素初始化为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];
		}
	}

}

分析每一步的过程:
初始化:
【C++】vector模拟实现_第45张图片
整体赋值:
【C++】vector模拟实现_第46张图片
更新”中间部分“的值
【C++】vector模拟实现_第47张图片
点击 vector 获取vector模拟实现的源代码

好的,到这里,vector部分的内容算是圆满结束了!下篇我们来学习带头结点的双向循环链表(list),感觉有所收获的读友们欢迎评论转发~~
我们下篇见~
【C++】vector模拟实现_第48张图片

你可能感兴趣的:(C++,c++,后端,数据结构,容器,开发语言)