目录
vector的结构
构造函数
拷贝构造函数
赋值运算符重载
返回迭代器
普通对象的迭代器
const对象的迭代器
重载[]
求vector元素的个数size
求vector的容量capacity
reserve进行扩容
resize函数
尾插pushback
尾删popback
插入数据insert
vector模拟实现整体代码
上期我们学习了vector的一些操作,为了更好地理解和掌握vector我们将从底层的角度模拟实现vector。
我们知道,string类的结构体是有一个指针变量_str,两个整型变量_size和_capacity组成的。但是在vector中,vector和string类的本质都是数组,既然string类用了这样的数据结构,那么vector类是不是也使用了这样的数据结构呢?道理是这样,但是库中并没有这样实现,而是使用了三个指针类型的成员变量去实现。
图示如下:
也就意味着,vector容器是使用了三个迭代器去实现的。
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
}
这是一个无参的构造函数。
template
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
这是一个使用迭代器区间进行初始化的构造函数。为什么要设置这个构造函数,主要还是为了后续拷贝构造函数深拷贝的现代写法,为了创建一个中间变量,所以就要用这个构造函数对这个重甲变量进行初始化。
vector(const vector& v)
//传统写法,先开空间,开好空间后进行赋值
/*iterator* _start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T) * v.size());
_finish = _start + v.size();
_endofstorage = _start + v.capacity();*/
//现代写法,考虑通过构建一个中间变量,先给中间变量进行赋值,然后交换中间对象和对象的成员变量。
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
vector tmp(v.begin(), v.end());
swap(_start, tmp._start);
swap(_finish, tmp._finish);
swap(_endofstorage, tmp._endofstorage);
}
这里需要注意的是,因为拷贝构造函数也是个构造函数,所以对成员变量也要进行初始化,这是因为中间我们的对象在与中间变量进行交换时,为了防止交换之后,中间变量的成员变量为野指针,所以必须进行初始化。
vector& operator= (const vector tmp)
{
swap(_start, tmp._start);
swap(_finish, tmp._finish);
swap(_endofstorage, tmp._endofstorage);
return *this;
}
这里也使用了深拷贝的现代写法,通过值传递创建了一个中间对象,然后交换对象与中间对象的值就可以完成赋值。
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
返回的是数组的第一个元素的地址和最后一个元素的下一个位置的地址。
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
const迭代器主要适用于const对象,但是普通对象仍然是可以进行调用的,因为权限缩小和权限不变是允许的,但是权限放大是不允许的。
T& operator[](size_t i)
{
return _start[i];
}
const T& operator [](size_t i)const
{
return _start[i];
}
当对象需要修改时就调用第一个函数,当对象只允许读,不允许写时,调用第二个函数。
size_t size() const
{
retrun _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
void reserve(int n)
{
if (n > capacity())
{
iterator* tmp = new T[n];
size_t size = size();
if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
}
delete[] _start;
_start = tmp;
_finish = _start + size;
_endofstorage = _start + n;
}
}
这里需要注意的是,要提前算好元素的个数,因为扩容之后_start指向了新的空间,此时如果继续使用size()去求元素的个数, 求出的元素个数有误。
void resize(int n, T val = T())
{
if (n < size())
{
finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish < _start+n)
{
*_finish = val;
_finish++;
}
}
}
与string类似,要分情况讨论。
void pushback(const T& x)
{
if (_finish == _endofstorage)
{
int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;
_finish++;
}
注意:尾插时要判断空间是否足够,否则要进行扩容,复用了reserve进行扩容。
void popback()
{
assert(_finish > _start);
--_finish;
}
iterator insert(iterator pos, T val)
{
assert(pos >= _start);
assert(pos <= _finish);
size_t length = pos - _start;
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
pos = _start + length;
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
_finish--;
*pos = val;
return pos;
}
注意:这里插入数据涉及到了迭代器失效问题,迭代器失效我们下期为大家讲述。
删除数据erase
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos;
while (begin < _finish - 1)
{
*begin = *(begin + 1);
begin++;
}
--_finish;
//这里要返回pos位置上的数据,是因为,可能会跳过元素,这里有几种情况,所以我们必须返回删除之后,正确的迭代器的位置,所以我们要返回pos
// 1 2 3 4 5 -> 正常
// 1 2 3 4 -> 崩溃
// 1 2 4 5 -> 没删除完
return pos;
}
注意:删除数据时,也会涉及到迭代器失效的问题,我们下期给大家讲述。
//vector的模拟实现
#include
using namespace std;
namespace yjd{
template
class vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
//构造函数、
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
}
//迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
//const迭代器
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
//重载[]
T& operator[](size_t i)
{
return _start[i];
}
const T& operator [](size_t i)const
{
return _start[i];
}
//求size,设置成const成员函数之后,普通对象和const对象都可以去进行调用
size_t size() const
{
retrun _finish - _start;
}
//求capacity
size_t capacity() const
{
return _endofstorage - _start;
}
//reserve进行扩容
void reserve(int n)
{
if (n > capacity())
{
iterator* tmp = new T[n];
size_t size = size();
if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
}
delete[] _start;
_start = tmp;
_finish = _start + size;
_endofstorage = _start + n;
}
}
//resize
void resize(int n, T val = T())
{
if (n < size())
{
finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish < _start+n)
{
*_finish = val;
_finish++;
}
}
}
//尾插,pushback
void pushback(const T& x)
{
if (_finish == _endofstorage)
{
int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;
_finish++;
}
//要实现拷贝构造函数的深拷贝,得先构造一个使用迭代器实现的构造函数
template
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造函数
vector(const vector& v)
//传统写法,先开空间,开好空间后进行赋值
/*iterator* _start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T) * v.size());
_finish = _start + v.size();
_endofstorage = _start + v.capacity();*/
//现代写法,考虑通过构建一个中间变量,先给中间变量进行赋值,然后交换中间对象和对象的成员变量。
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
vector tmp(v.begin(), v.end());
swap(_start, tmp._start);
swap(_finish, tmp._finish);
swap(_endofstorage, tmp._endofstorage);
}
//赋值运算符重载
vector& operator= (const vector tmp)
{
swap(_start, tmp._start);
swap(_finish, tmp._finish);
swap(_endofstorage, tmp._endofstorage);
return *this;
}
//析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
//尾删数据
void popback()
{
assert(_finish > _start);
--_finish;
}
//插入数据,插入数据时,要注意迭代器失效的情况,插入数据时,涉及到了扩容,扩容之后pos的位置发生了变化,所以就会导致之前的pos失效,所以就要重新设置pos的位置,但是这里的设置都是形参在变化,因为这里的是值传递,所以为了让我们知道实参对应的位置失效了,最终得返回pos位置,为扩容之后真正的位置
iterator insert(iterator pos, T val)
{
assert(pos >= _start);
assert(pos <= _finish);
size_t length = pos - _start;
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
pos = _start + length;
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
_finish--;
*pos = val;
return pos;
}
//删除数据,删除数据时,也会有迭代器失效的问题
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos;
while (begin < _finish - 1)
{
*begin = *(begin + 1);
begin++;
}
--_finish;
//这里要返回pos位置上的数据,是因为,可能会跳过元素,这里有几种情况,所以我们必须返回删除之后,正确的迭代器的位置,所以我们要返回pos
// 1 2 3 4 5 -> 正常
// 1 2 3 4 -> 崩溃
// 1 2 4 5 -> 没删除完
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
总体来说,vector的模拟实现和string的模拟实现是类似的,因为它们本质上都是数组,但是vector使用了三个迭代器去实现,并且vector的模拟实现涉及到了迭代器失效的问题,迭代器失效我们会作为一个主题在下期为大家讲解。
好了,本期的内容到此结束^_^