本文将按照前后依赖逻辑链完成vector的模拟实现,重点介绍迭代器失效问题。完整的.h
文件附在最后(含测试代码)
正文开始@边通书
在源码中,vector中声明了如下成员变量 ——
template<class T, class Alloc = alloc>
class vector
{
public:
typedef T* iterator;
private:
iterator start;
iterator finish;
iterator end_of_storage;
};
它们的作用等同于我们之前的 _a
、_size
、_capacity
——
❤️ 构造函数
类似顺序表,初始化一定要都给空,不然push_back就会出问题。
//1.无参构造
vector()
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
❤️ 析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
T& operator[](size_t n)
{
assert(n <= size());
return _start[n];
}
const T& operator[](size_t n) const
{
assert(n <= size());
return _start[n];
}
普通迭代器 & const迭代器,const对象调用const成员函数返回const迭代器,老生常谈了
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
支持迭代器就支持范围for.
插入元素要考虑扩容问题。
❤️ reserve
同样需要深拷贝。
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t sz = size();
//若原来已有空间,则需要释放旧空间、拷贝数据
if (_start)
{
/*memcpy(tmp, _start, sizeof(T)*sz);
delete[] _start;*/
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
注:
注意更新*_finish*时,要提前把sz
保存起来。
// 错误示范,请勿模仿
_start = tmp;
_finish = _start + size();
_endofstorage = _start + n;
这样是有问题的,因为size()是指针-指针,预期的是旧空间的_finish - _start(0),但是start已经更新了,减完就是负值。
拷贝数据不能简单地memcpy ,这样虽然vector是深拷贝的,但其中的对象仍然是浅拷贝的。对于int没有问题,但是对于自定义类型就出问题了。因此我们需要更改 ——
❤️ resize
resize经典的三种情况,老生常谈了
void resize(size_t n,const T& val = T() )
{
if (n <= size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish != _start + n)
{
*_finish = val;
_finish++;
}
}
}
说明 ——
匿名对象像临时对象一样,具有常属性不可修改,必须加const
匿名对象T()
(生命周期在当前行)被const引用,会延长它的生命周期。引用销毁后,随之销毁(可验证)。
resize默认填充的是T类型的缺省值,int类型就是0;指针类型就是空指针;若是自定义类型,比如vector,那么就是调用的vector的构造函数,构造vector的匿名对象
❤️ push_back
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 :capacity()*2);
}
*_finish = x;
_finish++;
}
❤️ pop_back
void pop_back()
{
assert(_finish > _start);
_finish--;
}
❤️ 迭代器区间构造
//2.迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
说明 ——
一个类模板的成员函数又可以是一个函数模板,这样,迭代器区间[first,last)可以是自己类型的迭代器,又可以是任意类型的迭代器。只要这个迭代器解引用的数据能和T匹配。
同样的,一定要初始化,否则一会儿要push_back时候就会出问题(当初我写顺序表时候就吃过这个亏)。
关于InputIterator
名称的含义:函数模板的模板参数传迭代器区间是有命名规范的
迭代器分为四类,用来访问容器 ——
学习继承后可以有更好的理解,子类满足父类的所有特征,同时又具有新的特征。子类的迭代器都可以给父类的,反过来不行。
因此,虽然语法上模板参数可以是任意类型,但是又有一定要求。
❤️ 拷贝构造 - 现代写法
借助构造,实现过string类,这就是老生常谈了
void swap(vector<T>& v)
{
std::swap(_start,v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
// 拷贝构造2 - 现代写法 - 借助构造
// v2(v1);
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
//this->swap(tmp);
/*swap(_start, tmp._start);
swap(_finish,tmp._finish);
swap(_endofstorage, tmp._endofstorage);*/
}
❤️ 赋值重载 - 现代写法
// 赋值重载
// v1 = v3;
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
这里是传值调用,v是v3拷贝构造来的。在此,我们再次重新理解拷贝构造不能传值必须传引用,因为拷贝构造要先传参,传参又是一次拷贝构造,这样下去无限递归了!
我们按照惯常的逻辑实现insert:检查空间、挪动数据、插入数据(代码如下)。测试代码,发现insert插入第5个数据(即扩容时),出现了随机值
// 错误示范,请勿模仿
void insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
iterator end = _finish -1;
while (end >= pos)
{
*(end +1) = *end;
end--;
}
*pos = x;
_finish++;
}
测试代码 ——
//测试insert - 迭代器失效问题
void testVector4()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
v.insert(it, 20);
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
这是因为若insert时发生了扩容,会导致it指向的空间被释放,此时it还指向着已被释放的空间是野指针,这种问题,我们就叫做迭代器失效。
⭐️ 因此我们通过,增容时,提前算出相对位置,更新pos来解决。然而此时外边的it仍然是失效的,则通过传返回值(An iterator that points to the first of the newly inserted elements.),若之后还需要使用it,再根据需要接收返回值(不能传引用,传引用会引发其他问题)
❤️ 终版insert
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
// 扩容会导致迭代器失效,需要提前计算更新pos
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish -1;
while (end >= pos)
{
*(end +1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
对应测试代码 ——
//测试insert - 迭代器失效问题
void testVector4()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
it = v.insert(it, 20);
// 若insert时发生了扩容,会导致it指向的空间被释放
// it本质上是野指针,这种问题,我们就叫做迭代器失效
}
v.insert(it, 10);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
运行结果 ——
我们按照惯常的逻辑实现erase:挪动数据覆盖。因为不涉及重新开空间深拷贝,那这块儿确实没出现野指针的问题。
// 错误示范,请勿模仿
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
assert(_start < _finish);
iterator begin = pos +1;
while (begin < _finish)
{
*(begin - 1) = *begin;
begin++;
}
_finish--;
}
现在,要求实现删除v中所有偶数,测试代码 ——
//测试erase - 迭代器失效问题
void testVector5()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);
//删除数组中的偶数
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);
}
it++;
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
测试如下三种场景,却发现 ——
1 2 3 4 5 -> 正常(其实是巧合)
1 2 4 5 -> 没删利索
1 2 3 4 -> 崩了
以1245为例 ,这代表的是相连偶数的情况。删完2(挪动数据)后it++,导致后一个偶数没有判断,4刚好就逃过去了;
再分析1234,这代表的是最后一个数是偶数的情况。erase最后一个偶数,it++,此时it和_finish刚好错过,it如脱缰野马,越界访问(这时候你就说了,把!=改成<不行吗,这不符合迭代器的一般使用)
总结,erase(it)后,it指向的位置意义已经变了,直接++,会导致一些意料之外的结果,这也是迭代器失效的问题。
⭐️ 文档中写道,函数调用后会返回刚删除位置的下一个位置,即这个意义已经改变的pos (An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.)
❤️ 终版insert,添加返回值
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
assert(_start < _finish);
iterator begin = pos +1;
while (begin < _finish)
{
*(begin - 1) = *begin;
begin++;
}
_finish--;
return pos;
}
删除v中所有偶数的正确代码 ——
//测试erase - 迭代器失效问题
void testVector5()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);
}
else
{
it++;
}
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
这里要特别说明一下testVector6,是为了测试vector中是自定义类型的情况,比如string。这涉及深浅拷贝问题,reserve时若用memcpy就是浅拷贝,会有问题,就需要一个一个对象进行深拷贝赋值。
如果用memcpy,会出现长字符串是乱码,短字符串能正常打印的情况。
这是因为vs做了一些优化, 如果字符串较短(16),不会去堆上申请空间了,直接在对象中在栈上放一个小数组,用空间换取时间,所以幸存了。在Linux运行,全部都是乱码 ——
#include
#include
#include
#include
using namespace std;
namespace beatles
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//1.无参构造
vector()
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
//2.迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
// 拷贝构造1 - 传统写法
// 注:这里的memcpy同样是有问题的,要和reserve一样一个一个拷贝
//vector(const vector& v)
//{
// _start = new T[v.capacity()];
// _finish = _start + v.size();
// _endofstorage = _start + v.capacity();
// memcpy(_start, v._start, sizeof(T)*v.size());
//}
void swap(vector<T>& v)
{
std::swap(_start,v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
// 拷贝构造2 - 现代写法 - 借助构造
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
/*swap(_start, tmp._start);
swap(_finish,tmp._finish);
swap(_endofstorage, tmp._endofstorage);*/
}
// 赋值重载
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
T& operator[](size_t n)
{
assert(n <= size());
return _start[n];
}
const T& operator[](size_t n) const
{
assert(n <= size());
return _start[n];
}
void resize(size_t n, const T& val = 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())
{
T* tmp = new T[n];
size_t sz = size();
if (_start)
{
/*memcpy(tmp, _start, sizeof(T)*sz);
delete[] _start;*/
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 :capacity()*2);
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(_finish > _start);
_finish--;
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
// 扩容会导致迭代器失效,需要提前计算更新pos
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish -1;
while (end >= pos)
{
*(end +1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
assert(_start < _finish);
iterator begin = pos +1;
while (begin < _finish)
{
*(begin - 1) = *begin;
begin++;
}
_finish--;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
// 测试vector中的对象是自定义类型
// 若按照memecpy这种错误写法会出现乱码
void testVector6()
{
vector<string> v;
v.push_back("1111111111111111111111");
v.push_back("1111111111111111111111");
v.push_back("1111111");
v.push_back("1111111");
v.push_back("1111111");
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
}
//测试erase - 迭代器失效问题
void testVector5()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
//v.push_back(3);
v.push_back(4);
v.push_back(5);
//删除数组中的偶数
//测试三种场景
//1 2 3 4 5 -> 正常(巧合)
//1 2 4 5 -> 没删利索
//1 2 3 4 -> 崩了
//vector::iterator it = v.begin();
//while (it != v.end())
//{
// if (*it % 2 == 0)
// {
// v.erase(it);
// }
// it++;
//}
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);
}
else
{
it++;
}
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
//测试insert - 迭代器失效问题
void testVector4()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
it = v.insert(it, 20);
// 若insert时发生了扩容,会导致it指向的空间被释放
// it本质上是野指针,这种问题,我们就叫做迭代器失效
}
v.insert(it, 10);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
// 测试拷贝构造&赋值重载,
//同时测试了迭代器区间构造
void testVector3()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
vector<int> v2(v1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
vector<int> v3;
v3.push_back(10);
v3.push_back(20);
v3.push_back(30);
v1 = v3;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
//测试resize
void testVector2()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//1.
v.resize(6, 6);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//2.
v.resize(10);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//3.
v.resize(2);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
//测试尾插尾删,同时测试reserve
//遍历测试 [] & 迭代器
void testVector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
v.pop_back();
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
}
vector.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"vector.h"
int main()
{
//beatles::testVector1();
//beatles::testVector2();
//beatles::testVector3();
//beatles::testVector4();
//beatles::testVector5();
beatles::testVector6();
return 0;
}