SGI版本的vector的底层管理方式与我们先前学习的数据结构部分的顺序表,略有差别。它在底层是通过迭代器(原生态的指针)来管理空间的。具体的定义如下:
namespace gyj
{
template<class T>
class vector
{
public:
typedef T* iterator;
private:
iterator start;
iterator finish;
iterator end_of_storage;
};
}
有了上面的前提知识铺垫,接下来我们模拟实现构造析构相关的接口,并完成相应的测试。
对于构造函数,模拟实现以下接口:
(1)默认构造函数----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
,现在我们来对上述模块的代码进行测试,发现编译的时候报了这样的错误:
它告诉我们在区间构造的地方有非法的间接寻址!!
经过分析,我们发现是调用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;
}
}
结果正常输出:
注意:自定义类型的流输出运算符需要我们自己提供,具体方式如下:
(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的功能特性如下:
根据特性编写代码:
(1)测试size()、capacity()、empty()
(2)测试resize()
(3)测试reserve()
对于内置类型,我们的代码是正确的
(1)测试size()、capacity()、empty()
正常!
(2)测试resize()
测试resize的时候,我们发现执行结果出错了。分析得知,是reserve函数出错导致resize函数出错。因此,我们需要解决reserve函数内的错误!
(3)测试reserve()
单独测试reserve发现程序直接崩溃,这显然是reserve有着大问题~
,找到问题我们就深入函数内部分析解决即可
从我们的代码入手进行分析:
②接着调用reserve接口,将容量扩大至7
第一步:申请新空间
第二步,使用memcpy将旧空间的元素拷贝至新空间
ok,看到这里我们可以得到一个结论:
如果对象中涉及到资源管理时,一定不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,会引起内存泄露以及程序崩溃。
那么,我们代码的问题也就迎刃而解了~问题就出在memcpy上。那么我们如何解决呢?使用循环逐一拷贝即可!代码如下:
欧克,现在我们再次来测试resize接口和reserve接口
到这里,我们容量相关的测试,告一段落~
(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
;
(1)尾插 void push_back(const T& val);
(3)任意位置插入 iterator insert(iterator pos,const T& val);
(4)任意位置的删除iterator erase(iterator pos);
(6)交换两个容器 void swap(vector& v);
以杨辉三角为例,理解动态二维数组的申请及使用过程
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];
}
}
}
分析每一步的过程:
初始化:
整体赋值:
更新”中间部分“的值
点击 vector 获取vector模拟实现的源代码
好的,到这里,vector部分的内容算是圆满结束了!下篇我们来学习带头结点的双向循环链表(list),感觉有所收获的读友们欢迎评论转发~~
我们下篇见~