目录
迭代器其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,即如果继续使用已经失效的迭代器,程序可能会崩溃。
1、vector实现的底层成员变量
vector的迭代器失效问题,主要会发生在插入insert函数接口 以及 删除erase函数接口中。
那么首先我们需要知道vector的底层结构是如何实现的。
从上图可知,容器vector实现的成员变量 有迭代器start、迭代器finish、迭代器end_of_storage。
迭代器start指向的是容器vector里存放的第一个有效数据位置;
迭代器finish指向的是容器vector里存放的最后一个有效数据位置的下一个位置;
迭代器end_of_storage指向的是容器vector里容量capacity的下一个位置。
2、插入数据insert成员函数的模拟实现
2.1插入函数insert成员函数迭代器失效的问题
插入数据insert成员函数会发生迭代器失效的问题,所以我们通过其模拟实现来看看如何导致的迭代器失效的问题。
template<class T>
class vector
{
public:
//插入数据的函数
void insert(iterator pos, const T& x)
{
//断言:位置pos(迭代器)必须正常
assert(pos >= _start);
assert(pos <= _finish);
//空间不够就要扩容
if (_finish == _end_of_storage)
{
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要加加一次
//迭代器_finish指向最后一个有效数据的空间 的下一个
++_finish;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
这里会出现迭代器失效的是迭代器pos,即外部传进函数的迭代器pos,如果我们在使用插入函数insert后,在函数外部继续使用迭代器pos进行操作,可能就会出现问题。
主要原因是因为我们在函数内部会进行空间不够的扩容处理,即使用成员函数reverse,因为扩容会另外开辟新空间,所以原来的迭代器pos指向的空间就被销毁了,所以在函数内部会改变pos的值,而插入函数的是用的传值传参,所以改变函数内部的迭代器pos并不会影响外部的迭代器,所以如果发生了扩容处理,那么这个时候外部的迭代器就不能再使用,否则就会出现迭代器失效的 问题,导致系统奔溃。
2.2如何解决insert成员函数迭代器失效的问题
我们上面的模拟实现,与stl中标准库里vector的insert成员函数实现是一样的。
那么我们在模拟实现的基础上,如何解决这个问题了??
方法一:外部的迭代器pos最好 不要再使用。
方法二:返回改变的迭代器pos,及时更新外部迭代器pos。
但是方法二,库里的这个函数 不是这样设计的,库里这个函数并没有返回,和上面的代码一样。
对于方法二的模拟实现:
template<class T>
class vector
{
public:
//插入数据的函数
iterator insert(iterator pos, const T& x)
{
//断言:位置pos(迭代器)必须正常
assert(pos >= _start);
assert(pos <= _finish);
//空间不够就要扩容
if (_finish == _end_of_storage)
{
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要加加一次
//迭代器_finish指向最后一个有效数据的空间 的下一个
++_finish;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
将函数内部改变的迭代器pos作为函数值返回,然后在函数外部接收返回值,这样就能达到更新函数外部迭代器pos的值,那么这个时候,在函数外部继续使用该迭代器就不会出现迭代器失效的问题,也就不会导致系统崩溃。
再次说明,stl标准库vector里的insert函数,并没有设置返回值!
3、插入数据erase成员函数的模拟实现
3.1插入函数erase成员函数迭代器失效的问题
插入数据erase成员函数会发生迭代器失效的问题,所以我们通过其模拟实现来看看如何导致的迭代器失效的问题。
template<class T>
class vector
{
public:
//删除数据的函数
void erase(iterator pos)
{
// 挪动数据进行删除
iterator begin = pos + 1;
//将pos位置后的数据往前移动,是从前面的数据开始往前移动
while (begin != _finish) {
*(begin - 1) = *begin;
++begin;
}
//删除一个数据后,迭代器_finish要--
--_finish;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效。
但是,如果pos刚好是指向最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,该位置迭代器就失效了。
3.2如何解决erase成员函数迭代器失效的问题
方法:返回改变的迭代器pos,及时更新外部迭代器pos。
库里的这个函数 就是 这样设计的,这个和insert相反。
template<class T>
class vector
{
public:
//删除数据的函数
iterator erase(iterator pos)
{
// 挪动数据进行删除
iterator begin = pos + 1;
//将pos位置后的数据往前移动,是从前面的数据开始往前移动
while (begin != _finish) {
*(begin - 1) = *begin;
++begin;
}
//删除一个数据后,迭代器_finish要--
--_finish;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
将函数内部改变的迭代器pos作为函数值返回,然后在函数外部接收返回值,这样就能达到更新函数外部迭代器pos的值,那么这个时候,在函数外部继续使用该迭代器就不会出现迭代器失效的问题,也就不会导致系统崩溃。