目录
调试可以看出start、finish和end_of_storage使用迭代器是有好处的,能应对不同的模版!
1.知识回顾
与vector1相关的知识点:
CC29.【C++ Cont】STL库:动态顺序表(vector容器)
2.SGI STL的vector
模拟实现vector前,先看看SGI STl中的vector的结构:
使用《STL源码剖析》提到的STL v3.0中的vector,先看框架:
可以看到有vector类,其外有operator==、operator<、operator=、insert_aux和insert函数
再看成员变量:肯定在vector类里面:
start、finish、end_of_storage的含义可以参见《STL源码剖析》的示意图:
类比之前CD37.【C++ Dev】string类的模拟实现(1)文章string的模拟实现,
class string
{
//......
private:
char* _str;
size_t _size;
size_t _capacity;
};
_str相当于这里的start,_size相当于这里的finish,_capacity相当于这里的end_of_storage
发现这3个成员变量的类型为iterator,而且iterator实际上是指针(因为vector是一段连续的空间,用指针利于访问)
typedef value_type* iterator;
再看构造函数:
vector() : start(0), finish(0), end_of_storage(0) {}
使用初始化成员列表,一开始vector为空,所以全初始化为0
当然也可以使用C++11的缺省值的写法:
namespace mystl
{
template<class T>
class vector
{
public:
typedef T value_type;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef size_t size_type;
//各种成员函数
iterator start = 0;
iterator finish = 0;
iterator end_of_storage = 0;
};
}
其余函数同理分析
3.模拟实现
实现一个简化版本的vector
finish指向最后一个有效元素的下一个位置,end_of_storage指向存储空间的结尾
框架
保持和SGI STL一样的风格:
namespace mystl
{
template<class T>
class vector
{
public:
typedef T value_type;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef size_t size_type;
//各种成员函数
iterator start;
iterator finish;
iterator end_of_storage;
};
}
构造函数
全部填0就行
vector() : start(0), finish(0), end_of_storage(0) {}
capacity函数
size_type capacity() const
{
return end_of_storage - start;
}
size函数
参数保持一样的类型:
size_type size() const
{
return finish - start;
}
begin函数和end函数
参数保持一样的类型:
实现两个:
iterator begin()
{
return start;
}
const_iterator begin() const
{
return start;
}
实现两个:
iterator end()
{
return finish;
}
const_iterator end() const
{
return finish;
}
reserve函数
参数保持一样的类型:
和CD37.【C++ Dev】string类的模拟实现(1)文章的reserve实现思想类似,在reserve中必须检查n和capacity()的大小关系,别人调用reserve不一定会提前检查
void reserve(size_type n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t len = size();//delete前先保存元素的个数!
memmove(tmp,start , size()* sizeof(value_type));
delete[] start;
start = tmp;
finish = start + len;
end_of_storage = start + n;
}
}
(开空间使用new,不使用空间配置器,简化操作)
capacity()返回的是元素的个数,memove函数第三个参数是字节数,需要写成capacity()*sizeof(value_type)
(sizeof(value_type)是单个元素所占的空间大小,单位是字节)
如果start是有效的,肯定没问题,但如果是刚初始化的vector,从构造函数来看,刚刚初始化时立刻指向reserve,start为空指针,在执行memmove时也没有问题
再看为什么需要定义len:
扩容后,一般是异地扩容,原来的start和扩容后的start指向的地址肯定不同,为了定位扩容后的finish,为了让finish移到新位置,必须先算出原来有效元素的个数,使用len记录
注意:必须在delete前先保存元素的个数!
*注:这个reserve函数其实有错误,自定义类型的深拷贝会出问题,在之后的文章会改过来
push_back函数
尾插的前提:空间要足够,否则需要扩容
扩容分两种情况讨论
一开始start和end_of_storage都为空时尾插会有问题,需要先开辟空间后尾插,除此之外,start不为空,可以按一般的二倍扩容进行
void push_back(const value_type& val)
{
if (finish == nullptr)
reserve(4);
if (finish == end_of_storage)
reserve(capacity() * 2);
*finish = val;
finish++;
}
测试代码
1.T为内置类型
#include "vector.h"
void test1()
{
mystl::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//往下二倍扩容
v.push_back(1);
for (auto a : v)
std::cout << a << std::endl;
return;
}
int main()
{
test1();
return 0;
}
运行结果:
2.T为自定义类型
例如使用std::string作为自己实现的vector的模版
void test2()
{
mystl::vector<std::string> v_str;
v_str.push_back("abc");
v_str.push_back("de");
v_str.push_back("f");
v_str.push_back("ghijk");
for (auto a : v_str)
std::cout << a << std::endl;
return;
}
运行结果:
注:当push_back的vector个数超过4个时会出问题,原因在reserve函数,这个后面文章会修复
调试可以看出start、finish和end_of_storage使用迭代器是有好处的,能应对不同的模版!
push_back其实是一个个的string对象,不单单是字符串
同理,其实如果插入的string对象大于4个,也会出深拷贝问题,后面的博客将修复这个问题
析构函数
这样写是有问题的:
~vector()
{
delete[] start;
start = finish = end_of_storage = nullptr;
}
如果start为空,delete会有问题,因此需要先判断
~vector()
{
if (start)
{
delete[] start;
start = finish = end_of_storage = nullptr;
}
}