目录
1.成员变量的设计
2.迭代器以及接口设计
3.容量接口
4.reserve的设计
5.resize的设计
6.尾插尾删的设计
7.构造、析构函数
8.运算符重载
9.insert接口设计与迭代器失效
10.erase的设计和迭代器失效
11.双层深拷贝问题
12.完整代码
成员变量设计为三个迭代器,分别是:_start、_finish、_endofstorage。此设计原理参考STL源码。_start与_finish之间是存储有效元素的;_start与_endofstorage之间表容器的容量。
iterator _start;
iterator _finish;
iterator _endofstorage;
使用指针的方式实现迭代器,同时需要兼顾 const 对象和 const 迭代器。
typedef T* iterator;
typedef const T* const_iterator; //使用指针实现迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
size_t size()
{
return _finish - _start;
}
size_t capacity()
{
return _endofstorage - _start;
}
void clear()
{
_finish = _start;
}
bool empty()
{
return _finish == _start;
}
reserve如果涉及到扩容,统一采用异地扩容的方式。当参数小于当前容量时不作处理。
void reserve(const size_t& n)
{
if (n > capacity())
{
T* tmp = new T[n];
assert(tmp); //检查是否成功开辟
int oldsize = _finish - _start;
if (_start) //如果_start不为空需要拷贝数据
{
memcpy(tmp, _start, sizeof(T) * oldsize);
delete[] _start; //释放旧空间
}
//异地扩容,更新迭代器
_start = tmp;
_finish = _start + oldsize;
_endofstorage = _start + n;
}
}
resize需要分情况讨论,如果接收的参数小于当前容量时,空间不进行变动;如果小于当前有效元素个数时,需要更新_finish迭代器达到删除的效果。同时需要注意resize有第二个参数(官方手册)。
void resize(const size_t n, T val = T())
{
if (n > capacity) reserve(n); //大于当前容量,扩容
if (n > size())
{
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
else _finish = _start + n;
}
void push_back(const T& val)
{
if (_finish == _endofstorage) //是否需要扩容
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = val;
++_finish;
}
void pop_back()
{
assert(!empty()); //尾删容器不能为空
--_finish;
}
这里模拟实现:默认构造函数、范围拷贝、指定容器大小、拷贝构造函数(现代写法)、析构函数。
vector() //默认构造函数
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
template
vector(it first, it last) //范围拷贝构造
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造现代写法
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
vector(const vector& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector tmp(v.begin(), v.end());
swap(tmp);
}
vector(size_t n, const T& val = T()) //指定容器大小构造
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
~vector() //析构函数
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
上面的代码会出一个错误:
其原因在于:10 在编译器的视角中默认是一个 int 类型的数,而我们的接口设计是一个 size_t 类型的数,所以会存在一些类型提升。而我们又设计了一个模板,在编译器的视角看来,调用模板函数是开销更小的选择。
解决方案是添加一个重载:
vector(int n, const T& val = T()) //指定容器大小构造
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
需不需要使用 const 函数是看我们需求来的,这里给出设计原则:
上面的代码可能考虑的不够周全,读者可自行改进。下标运算符对于我们来说是可读可写的。
T& operator[](const size_t n)
{
assert(n >= 0 && n < size()); //防止越界
return _start[n];
}
const T& operator[](const size_t n) const
{
assert(n >= 0 && n < size()); //防止越界
return _start[n];
}
vector& operator=(vector v) //赋值运算符重载
{
swap(v);
return *this;
}
void insert(iterator pos,const T& val)
{
assert(pos >= _start && pos < _finish);
if (_finish == _endofstorage) //检查是否需要扩容
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
auto end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
}
这样设计的接口发生扩容时会产生迭代器失效,因为扩容是异地扩容,就会导致pos指向的空间已经被释放了,需要更新pos。因为是传值调用,我们还必须保证外部的迭代器能够及时更新。
iterator insert(iterator pos,const T& val)
{
assert(pos >= _start && pos < _finish);
if (_finish == _endofstorage) //检查是否需要扩容
{
size_t oldsize = _finish - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + oldsize; //扩容后,_start的指向已经更改,需要更新pos
}
auto end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos; //添加一个返回值供外部迭代器更新
}
外部需要继续使用迭代器的正确方法为:
在VS环境中使用标准库的insert接口,不按上面的方式使用迭代器,就会发生迭代器失效问题。
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
auto del = pos + 1;
while (del < _finish)
{
*(del - 1) = *del;
++del;
}
--_finish;
}
这样做会导致迭代器失效。其原因在于:删除最后一个元素,del的位置会指向一个不合法的位置,如果后续要对这个指针指向的内容进行读、写是非常危险的。所以与insert一样,VS的编译环境是使用insert和erase后迭代器失效。解决方案是返回指向删除元素位置的迭代器。
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
auto del = pos + 1;
while (del < _finish)
{
*(del - 1) = *del;
++del;
}
--_finish;
return pos;
}
外部调用时正确的使用方法:
使用标准库中的vector容器,不按上述方法使用会发生错误:
留意刚才的reserve接口:
void reserve(const size_t& n)
{
if (n > capacity())
{
T* tmp = new T[n];
assert(tmp); //检查是否成功开辟
int oldsize = _finish - _start;
if (_start) //如果_start不为空需要拷贝数据
{
memcpy(tmp, _start, sizeof(T) * oldsize);
delete[] _start; //释放旧空间
}
//异地扩容,更新迭代器
_start = tmp;
_finish = _start + oldsize;
_endofstorage = _start + n;
}
}
这么做会导致一个非常严重的野指针问题:
其原因在于:异地扩容之后,使用memcpy进行拷贝,而这个拷贝是一次浅拷贝,新空间和旧空间的对象都指向原来的_start指向的空间,当_start指向的空间释放后,新空间的对象_start就是野指针了,所以打印的就是乱码。当函数栈帧退出时又析构一次,就会造成一次严重的野指针问题。
所以我们的解决方案是不使用memcpy进行浅拷贝,而是使用赋值运算符重载进行深拷贝。
void reserve(const size_t n)
{
if (n > capacity())
{
int oldsize = _finish - _start;
T* tmp = new T[n];
assert(tmp); //检查是否成功开辟
if (_start) //如果_start不为空需要拷贝数据
{
//memcpy(tmp, _start, sizeof(T) * oldsize);
for (size_t i = 0; i < size(); i++)
{
tmp[i] = _start[i]; //使用赋值运算符重载
}
delete[] _start; //释放旧空间
}
//异地扩容,更新迭代器
_start = tmp;
_finish = _start + oldsize;
_endofstorage = _start + n;
}
}
#include
#include
#include
namespace ly
{
template
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator; //使用指针实现迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
vector() //默认构造函数
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
~vector() //析构函数
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
template
vector(it first, it last) //范围拷贝构造
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造现代写法
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
vector(const vector& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector tmp(v.begin(), v.end());
swap(tmp);
}
vector(size_t n, const T& val = T()) //指定容器大小构造
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
vector(int n, const T& val = T()) //指定容器大小构造
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
T& operator[](const size_t n)
{
assert(n < size()); //防止越界
return _start[n];
}
const T& operator[](const size_t n) const
{
assert( n < size()); //防止越界
return _start[n];
}
vector& operator=(vector v)
{
swap(v);
return *this;
}
void reserve(const size_t n)
{
if (n > capacity())
{
int oldsize = _finish - _start;
T* tmp = new T[n];
assert(tmp); //检查是否成功开辟
if (_start) //如果_start不为空需要拷贝数据
{
//memcpy(tmp, _start, sizeof(T) * oldsize);
for (size_t i = 0; i < size(); i++)
{
tmp[i] = _start[i]; //使用运算符重载
}
delete[] _start; //释放旧空间
}
//异地扩容,更新迭代器
_start = tmp;
_finish = _start + oldsize;
_endofstorage = _start + n;
}
}
void resize(const size_t n, T val = T())
{
if (n > capacity) reserve(n); //大于当前容量,扩容
if (n > size())
{
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
else _finish = _start + n;
}
size_t size()
{
return _finish - _start;
}
size_t capacity()
{
return _endofstorage - _start;
}
void clear()
{
_finish = _start;
}
bool empty()
{
return _finish == _start;
}
void push_back(const T& val)
{
if (_finish == _endofstorage) //是否需要扩容
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = val;
++_finish;
}
void pop_back()
{
assert(!empty()); //尾删容器不能为空
--_finish;
}
iterator insert(iterator pos,const T& val)
{
assert(pos >= _start && pos < _finish);
if (_finish == _endofstorage) //检查是否需要扩容
{
size_t oldsize = _finish - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + oldsize; //扩容后,_start的指向已经更改,需要更新pos
}
auto end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos; //添加一个返回值供外部迭代器更新
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
auto del = pos + 1;
while (del < _finish)
{
*(del - 1) = *del;
++del;
}
--_finish;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}