文章目录
前言
要模拟实现前:我们最主要是熟悉使用vector容器先,基本的使用我在好早一篇文章有介绍vector的使用,有需要可以看看c++基础 STL 第一篇: (初识STL 和 vector 容器)
vector的成员数据
对于一个vector来说:底层就是一个数组来实现;我们用模板来封装它,其实我们就是模板STL里的vector写的,
这里的iterator就是一个指针,只不过我们给他重命名而已,这也是模仿STL里面的写法的;
template<class T>
class vector
{
public:
typedef T* iterator;//可读可写迭代器
typedef const T* const_iterator; //只读迭代器
private:
iterator _start; //表示指向数组的头指针
iterator _finish;//表示指向数组的最后一个元素的下一个位置
iterator _end_of_stroage;//表示数组的容量
};
vector的默认构造函数
对于构造函数的调用有很多方式:
这里先实现默认构造:
vector()
:_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr)
{}
vector的拷贝构造和赋值运算
对于拷贝构造和赋值运算符都要完成深拷贝
逻辑也很简单:
先开辟一个新空间,把旧空间数据拷贝过去,就可以;
vector(const vector<T>& v)
{
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T).v.size()); //注意这里也会引发浅拷贝的问题
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
但是这种写法,没有以下的写法好:
//通过迭代器区间的构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//现代写法的拷贝构造
vector(const vector<T>& v)
{
vector<T> temp(v.begin(), v.end());
std::swap(_start, temp._start);
std::swap(_finish, temp._finish);
std::swap(_end_of_storage, temp._end_of_storage);
}
上面第一个函数模板:是利用区间来构造出vector的构造函数,用迭代器区间初始化;
一旦有了这个构造函数,我们就可以在拷贝构造函数里:先复用该迭代器区间的的构造函数,构造一个临时对象,
再去交换this和临时对象的数据,即可完成拷贝;
对于赋值运算符,也可以这么做,甚至我们在传参时候,不传引用,直接传值,这样就可以在参数直接构造出对象了:
vector<T>& operator(const vector<T> v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
return *this;
}
vector的迭代器
vector的迭代器种类也很多:
这里就只实现最常用的两种迭代器:
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin() const { return _start; }
const_iterator end() const { return _start; }
只读迭代器和可读可写迭代器:都是返回起始位置和最后一个位置的下一个位置的迭代器;
vector的[ ] 和 size 和 capacity
size_t size() const
{
//迭代器相减就是它们的大小
return _finish - _start;
}
size_t capacity() const
{
//迭代器相减就是它们的容量
return _end_of_storage - _start;
}
T& operator[] (size_t pos)
{
//断言防止越界访问
assert(pos < size());
//return _start[pos]; //同下
return *(_start + pos);
}
vector的resize和 reserve
这两个都是扩容函数:只不过resize扩容并初始化,而reserve不会;
对于reserve我们只需要考虑,插入的数据大于容量就扩容即可:
void reserve(size_t n) //扩容
{
if (n > capacity())
{
//扩容
size_t sz = size(); //提前保存size()大小
T* temp = new T[n];
if (_start) //旧空间不为空
{
//拷贝旧数据到新空间
memcpy(temp, _start, sizeof(T)*size());
//释放旧数据空间
delete[] _start;
}
_start = temp;//再让_start指向temp
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
这里要注意的一个问题就是:扩容前需要提前保存旧指针的size大小,为了是扩容过后,_finish能够正确的指向需要的位置;
假如没有提前保存size大小的分析:
上面的扩容还是会出大问题:其中出问题就是在于memcpy
,因为memcpy
是字节拷贝,也就是浅拷贝,很容易引发析构两次的问题:
所以我们要解决这个问题:
void reserve(size_t n) //扩容
{
if (n > capacity())
{
//扩容
size_t sz = size(); //提前保存size()大小
T* temp = new T[n];
if (_start) //旧空间不为空
{
//拷贝旧数据到新空间
for(size_t i = 0; i< sz;i++){
temp[i] = _start[i]; //这里调用赋值运算符,完成深拷贝
}
//释放旧数据空间
delete[] _start;
}
_start = temp;//再让_start指向temp
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
对于resize函数依旧很简单:
void resize(size_t n, const T& = T())
{ //如果扩容的大小小于原始数据大小
if (n <size())
{
_finish = _start + n;
}
else//如果扩容的大小大于原始数据大小
{
if (n > capacity())
{
reserve(n);
}
//往里添数据
while (_finish != _start+n)
{
*_finish = val;
++_finish;
}
}
}
vector的push_back和pop_back
这个逻辑很简单的:需要注意的是,插入数据四皇后,判断不要超过容量;
删除数据时候,不要超过起始位置即可;
void push_back(const T& x)
{
if (_finish >= _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x; //尾插数据
_finish++;
}
void pop_back()
{
assert(_finish > _start);
--_finish;
}
vector的insert和erase(会产生迭代器失效问题)
iterator insert(iterator pos, const T& v)//其实插入后的效果就是:在pos的前面插入v值
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == capacity())
reserve(capacity() == 0 ? 4 : capacity() * 2);
//插入逻辑
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = v;
_finish++;
return pos;
}
这样的插入逻辑会导致迭代器失效,是pos的迭代器失效了(pos是指向原来空间的位置)
因为当插入数据在容量扩容时候,我们的this对象就指向其他的新空间了
而原来的pos迭代器还是指向旧的空间,此时再扩容的情况下,我们还是用
pos位置的迭代器,就会发生越界(发生在while条件判断里),因为旧空间在扩容时候就失效了;
//解决pos迭代器失效的办法:就是在扩容之前保存pos位置的距离
//同时扩容成功后,更新pos的位置
//但是这个函数,在使用者调用时候,本身就会导致失效问题:只要发生扩容,调用者拿到insert返回值就会失效;
iterator insert(iterator pos, const T& v)//其实插入后的效果就是:在pos的前面插入v值
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == capacity()){
size_t len = pos - _start; //提前保存pos位置的距离
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len; //扩容后,更新pos的位置
}
//插入逻辑
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = v;
_finish++;
return pos;
}
删除:
对于删除逻辑也很简单
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函数的使用也会导致迭代器失效的问题:
当调用这使用该函数时候,传入的迭代器实参意义会发生变化;
它不再指向被删除的元素了,而是指向被删除元素下一个位置的那个元素;
对于vector可能会导致其迭代器失效的操作有:
会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、
push_back等。
vector 类的全部代码
#pragma once
namespace xjh
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
public:
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin() const { return _start; }
const_iterator end() const { return _start; }
vector()
:_start(nullptr),
_finish(nullptr),
_end_of_storage(nullptr)
{}
//通过迭代器区间的构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//现代写法的拷贝构造
vector(const vector<T>& v)
{
vector<T> temp(v.begin(), v.end());
std::swap(_start, temp._start);
std::swap(_finish, temp._finish);
std::swap(_end_of_storage, temp._end_of_storage);
}
//现代写法的赋值运算符
vector<T>& operator(const vector<T> v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
return *this;
}
T& operator[] (size_t pos)
{
assert(pos < size());
//return _start[pos]; //同下
return *(_start + pos);
}
size_t capacity() const
{
return _end_of_storage - _start;
}
size_t size() const
{
return _finish - _start;
}
void resize(size_t n, const T& = T())
{
if (n <size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
//往里添数据
while (_finish != _start+n)
{
*_finish = val;
++_finish;
}
}
}
void reserve(size_t n) //扩容
{
if (n > capacity())
{
//扩容
size_t sz = size(); //提前保存size()大小
T* temp = new T[n];
if (_start) //旧空间不为空
{
拷贝旧数据到新空间
//这种方式会引发浅拷贝的问题
//memcpy(temp, _start, sizeof(T)*size());
//这里调用赋值运算符重载,深拷贝
for (szie_t i = 0; i > sz; i++){
temp[i] = _start[i];
}
//释放旧数据空间
delete[] _start;
}
_start = temp;//再让_start指向temp
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void push_back(const T& x)
{
if (_finish >= _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x; //尾插数据
_finish++;
}
void pop_back()
{
assert(_finish > _start);
--_finish;
}
iterator insert(iterator pos, const T& v)//其实插入后的效果就是:在pos的前面插入v值
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == capacity()){
size_t len = pos - _start; //提前保存pos位置的距离
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len; //扩容后,更新pos的位置
}
//插入逻辑
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = v;
_finish++;
return 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;
}
};
}