模拟实现vector类
- 前言
- 一、迭代器
- 二、重载 [ ]
- 三、构造函数相关(重点)
- (1)构造函数
- (2)构造并使用n个值为value的元素初始化
- (3)区间构造
- (4)拷贝构造
- 三、析构函数
- 四、[赋值运算符重载](https://so.csdn.net/so/search?q=%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6%E9%87%8D%E8%BD%BD&spm=1001.2101.3001.7020)
- 五、reserve的实现--改变容器容量大小
- 五、resize的实现--改变容器有效字符个数
- 六、几种简单接口(empty、size、capacity、clear)
- 七、push_back和pop_back的实现
- 八、insert的实现(重点)--迭代器失效问题一
- 八、erase的实现(重点)--迭代器失效问题二
前言
类框架与之前string类的模拟实现类似,区别在于vector 是用 _finish 和 _end_of_storage 两个指针来维护空间,但本质上其实是一样的 ,
_size = _finish - _start
_capacity = _end_of_storage - _start
class vector
{
public:
//成员函数
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
}
接下来介绍成员函数的实现。
一、迭代器
iterator 与 const_iterator 的作用:
遍历容器内的元素,并访问这些元素的值。
iterator 与 const_iterator 的区别:
iterator 可以改元素值,但 const_iterator 不可以改元素值。
const_iterator 对象可以用于 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;
}
二、重载 [ ]
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
三、构造函数相关(重点)
(1)构造函数
直接将所有的成员变量置为空即可
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
(2)构造并使用n个值为value的元素初始化
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);
}
}
其中,第二个参数val是一个缺省参数。对于没有给定确定值的时候,有两种情况:
(1)如果T是内置类型,T()的值为0;
(2)如果T是自定义类型,T()调用的就是该自定义类型的默认构造函数。
因此,对于自定义类型一定要有默认构造函数,否则会报错!
重点在于第一个参数的类型,只能是int,如果是size_t类型,会报错。原因如下:
接口测试部分
构造函数错误调用问题
(3)区间构造
//迭代器区间构造
template <class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
为什么单独定义一个这样的函数模板呢?template <class InputIterator>
答:这是因为vector是一个底层连续的容器,它可以用来存储任意类型的数据,
而这里使用区间的方式来初始化vector的容器就需要做到对任意数据都可以进行初始化,因此就需要以模板的形式给出。
(4)拷贝构造
拷贝构造有三种实现方式,最推荐的当然是通过vector内部的swap()
函数实现
参数v是const修饰的,它只能调用const类型的成员函数,因此这里必须调用const修饰的迭代器:
//拷贝构造 -- 现代写法
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end()); //复用构造函数和swap函数
swap(tmp);
}
//写法2
//vector(const vector<T>& v)
//{
// T* tmp = new T[v.capacity()];
// memcpy(tmp, v._start, sizeof(T) * v.capacity());
// _start = tmp;
// _finish = _start + v.size();
// _end_of_storage = _start + v.capacity();
//}
//写法3
//vector(const vector<T>& v)
// : _start(nullptr)
// , _finish(nullptr)
// , _end_of_storage(nullptr)
//{
// reserve(v.capacity());
// for (size_t i = 0; i < v.size(); i++)
// push_back(v[i]);
//}
三、析构函数
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
四、赋值运算符重载
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
五、reserve的实现–改变容器容量大小
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldSize = size();
T* tmp = new T[n];
if (_start)
{
// 扩容必须深拷贝,否则会导致迭代器失效
for (size_t i = 0; i < oldSize; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = tmp + oldSize;
_endofstorage = _start + n;
}
}
五、resize的实现–改变容器有效字符个数
void resize(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;
}
}
六、几种简单接口(empty、size、capacity、clear)
bool empty() const
{
return _finish == _start;
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
void clear()
{
_finish = _start;
}
七、push_back和pop_back的实现
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
*_finish = x;
++_finish;
}
void pop_back()
{
assert(!empty());
--_finish;
}
八、insert的实现(重点)–迭代器失效问题一
//迭代器失效:扩容引起的野指针问题
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos < _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start; //记录pos,避免扩容后pos变为野指针
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
pos = _start + len; //更新pos
}
//挪动数据
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
迭代器失效问题一:这里是由于insert之后pos 的意义变了–不再指向原来的元素,而是指向新插入的元素。因此,如果不读pos进行更新,pos就会成为野指针。
八、erase的实现(重点)–迭代器失效问题二
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *(begin);
++begin;
}
--_finish;
return pos;
}
迭代器失效问题二:这里是由于erase之后pos 的意义变了,分为两种情况:
(1)删除元素后pos 不再指向原来的元素,而是指向该元素的后一个元素,所以 erase 之后会导致一个元素被跳过;
(2)在极端情况下–删除最后一个元素之后,pos 就等于 _finish,会发生越界。
因此insert 和 erase 之后迭代器失效,必须更新后才能再次使用。
具体的更新措施: