迭代器的应用实例(vector为例)
在博客上看到了许多大佬对于迭代器即迭代器失效问题的解读,都没有讲到其底层原理,于是就想着剖析一下为什么迭代器会失效,以及应用场景。写此博客简单剖析一下STL——iterator底层的实现原理以及失效原理
#include <vector>
using namespace std;
int main()
{
std::vector<int> vec;//实例化容器对象
for(int i = 0;i < 10;i++)
{
vec.push_back(i);
}
vector::iterator it = vec.begin();//实例化迭代器对象,使其底层指针指向容器首地址
for(;it != vec.end();++it)
{
cout << *it << " ";
}
return 0;
}
以上代码就是用迭代器遍历了容器中的所有元素
迭代器基础概念
- 迭代器是什么?
迭代器是一种检查容器内部元素并遍历元素的数据类型 - 迭代器用来做什么?
迭代器可以透明化的遍历容器的底层元素,又不暴露对象的内部表示,用迭代器来遍历容器,我们无需关心容器底层的数据结构到底是什么,因为在底层已经封装好了迭代器的一些运算操作,迭代器可以用同样的方式来遍历不同类型的容器 - 使用迭代器的好处
对于用户而言不用知道不同容器底层的数据结构,可以通过迭代器进行遍历,方便了使用者 - 迭代器的运算操作
包括it++,it–,ti1 - it2,it1 != it2等运算操作,底层都是通过运算符重载来完成的。 - 迭代器的类型
Input iterator:向前读取
output iterator:向前写入
forward iterator:向前读取和写入
Bidirectional iterator:向前和向后读取和写入
Random access:随机存取,可以读取和写入
我们一般使用的迭代器是Random
string类迭代器简单实现
#include <iostream>
#include <string>
using namespace std;
class myString
{
public:
myString(const char *p = nullptr)//普通构造
{
if (p != nullptr)
{
str = new char[strlen(p) + 1];
strcpy(str,p);
}
else
{
str = new char[1];
str[0] = 0;
}
}
myString(const myString& scr)//拷贝构造,因为是深拷贝,所以得自己写拷贝构造
{
str = new char[strlen(scr.str) + 1];
strcpy(str,scr.str);
}
myString& operator=(const myString& scr)
{
if (this == &scr)
{
return *this;
}
delete[]str;
str = new char[strlen(scr.str) + 1];
strcpy(str, scr.str);
}
~myString()
{
delete[]str;
str = nullptr;
}
bool operator>(const myString &scr)
{
return strcmp(str, scr.str) > 0;
}
bool operator<(const myString &scr)
{
return strcmp(str, scr.str) < 0;
}
bool operator==(const myString &scr)
{
return strcmp(str, scr.str) == 0;
}
char& operator[](int index)
{
return str[index];
}
int length()
{
return strlen(str);
}
const char* c_str()const
{
return str;
}
class iterator//是用来透明化遍历容器的
{
public:
iterator(char *src = nullptr)
{
p = src;
}
bool operator!=(const iterator &it)
{
return p != it.p;
}
iterator& operator++()
{
++p;
return *this;
}
iterator operator++(int)
{
return iterator(p++);
}
char& operator*()
{
return *p;
}
private:
char *p;
};
iterator begin() { return iterator(str); }
iterator end() { return iterator(str + length()); }
friend ostream& operator << (ostream &out, const myString scr);
friend istream& operator >> (istream& in, const myString& scr);
friend myString operator+(const myString& str1, const myString& str2);
private:
char *str;
};
myString operator+(const myString& str1, const myString& str2)
{
myString temp;
temp.str = new char[strlen(str1.str) + strlen(str2.str) + 1];
strcpy(temp.str, str1.str);
strcat(temp.str, str2.str);
return temp;
}
ostream& operator << (ostream &out, const myString scr)
{
out << scr.str;
return out;
}
istream& operator >> (istream& in, const myString& scr)
{
in >> scr.str;
return in;
}
int main()
{
myString str1;
myString str2 = "aaa";
myString str3 = "bbb";
myString str4 = str2 + str3;
myString str5 = str2 + "ccc";
myString str6 = "ddd" + str2;
cout << "str6:" << str6 << endl;
if (str5 > str6)
{
cout << str5 << ">" << str6 <<endl;
}
else
{
cout << str5 << "<" << str6<<endl;
}
int len = str6.length();
for (int i = 0; i < len; i++)
{
cout << str6[i] << " ";
}
cout << endl;
char buff[1024] = { 0 };
strcpy(buff,str6.c_str());
cout << "buff:" << buff << endl;
for (auto it = str6.begin(); it != str6.end(); it++)
{
cout << *it << " ";
}
cout << endl;
for (char ch : str6)//也是使用迭代器进行访问的
{
cout << ch << " ";
}
cout << endl;
return 0;
}
将iterator类嵌套在vector和string内部,我们会发现迭代器的底层就是一个指针,而迭代器和指针的区别在哪儿呢,如果说我们在类外定义一个指针来迭代容器元素,首先,会破坏容器底层数据的私有性,还有就是对于不同的容器迭代的方法也有所不同,而迭代器就实现了,对所有容器都可以使用同一种方式来迭代,至于迭代的一些运算符重载都封装成了迭代器的成员方法里,包括了++
vector迭代器简单实现
#include "pch.h"
#include <iostream>
using namespace std;
#if 0
// 定义容器的空间配置器,和C++标准库的allocator实现一样
template<typename T>
struct Allocator
{
T* allocate(size_t size) // 负责内存开辟
{
return (T*)malloc(sizeof(T) * size);
}
void deallocate(void *p) // 负责内存释放
{
free(p);
}
void construct(T *p, const T &val) // 负责对象构造
{
new (p) T(val); // 定位new
}
void destroy(T *p) // 负责对象析构
{
p->~T(); // ~T()代表了T类型的析构函数
}
};
/*
容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间配置器来实现
*/
template<typename T, typename Alloc = Allocator<T>>
class vector
{
public:
vector(int size = 10)
{
// 需要把内存开辟和对象构造分开处理
//_first = new T[size];
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~vector()
{
// 析构容器有效的元素,然后释放_first指针指向的堆内存
// delete[]_first;
for (T *p = _first; p != _last; ++p)
{
_allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作
}
_allocator.deallocate(_first); // 释放堆上的数组内存
_first = _last = _end = nullptr;
}
vector(const vector<T> &rhs)
{
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator=(const vector<T> &rhs)
{
if (this == &rhs)
return *this;
//delete[]_first;
for (T *p = _first; p != _last; ++p)
{
_allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作
}
_allocator.deallocate(_first);
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T &val) // 向容器末尾添加元素
{
if (full())
expand();
//*_last++ = val; _last指针指向的内存构造一个值为val的对象
_allocator.construct(_last, val);
_last++;
}
void pop_back() // 从容器末尾删除元素
{
if (empty())
return;
// erase(it); verify(it._ptr, _last);
// insert(it, val); verify(it._ptr, _last);
verify(_last - 1, _last);
//--_last; // 不仅要把_last指针--,还需要析构删除的元素
--_last;
_allocator.destroy(_last);
}
T back()const // 返回容器末尾的元素的值
{
return *(_last - 1);
}
bool full()const { return _last == _end; }
bool empty()const { return _first == _last; }
int size()const { return _last - _first; }
T& operator[](int index) // vec[2]
{
if (index < 0 || index >= size())
{
throw "OutOfRangeException";
}
return _first[index];
}
// insert erase
// #1迭代器一般实现成容器的嵌套类型
class iterator
{
public:
friend class vector<T, Alloc>;
iterator(vector<T, Alloc> *pvec=nullptr
, T *ptr = nullptr)
:_ptr(ptr), _pVec(pvec)
{
Iterator_Base *itb =
new Iterator_Base(this, _pVec->_head._next);
_pVec->_head._next = itb;/构造好了的迭代器的节点进行头插法
}
bool operator!=(const iterator &it)const
{
// 检查迭代器的有效性
if (_pVec == nullptr || _pVec != it._pVec)//如果_pVec为空,说明迭代器失效
{
throw "iterator incompatable!";
}
return _ptr != it._ptr;
}
void operator++()
{
// 检查迭代器的有效性
if (_pVec == nullptr)
{
throw "iterator invalid!";
}
_ptr++;
}
T& operator*()
{
// 检查迭代器的有效性
if (_pVec == nullptr)
{
throw "iterator invalid!";
}
return *_ptr;
}
const T& operator*()const
{
// 检查迭代器的有效性
if (_pVec == nullptr)
{
throw "iterator invalid!";
}
return *_ptr;
}
private:
T *_ptr;
// 当前迭代器迭代器是哪个容器对象
vector<T, Alloc> *_pVec;
};
// 需要给容器提供begin和end方法
iterator begin() { return iterator(this, _first); }
iterator end() { return iterator(this, _last); }
// 检查迭代器失效,如果失效,将——pVec置为空
void verify(T *first, T *last)
{
Iterator_Base *pre = &this->_head;
Iterator_Base *it = this->_head._next;
while (it != nullptr)
{
if (it->_cur->_ptr > first && it->_cur->_ptr <= last)
{
// 迭代器失效,把iterator持有的容器指针置nullptr
it->_cur->_pVec = nullptr;
// 删除当前迭代器节点,继续判断后面的迭代器节点是否失效
pre->_next = it->_next;
delete it;
it = pre->_next;
}
else
{
pre = it;
it = it->_next;
}
}
}
// 自定义vector容器insert方法的实现
iterator insert(iterator it, const T &val)
{
/*
1.不考虑扩容 verify(_first - 1, _last);
2.不考虑it._ptr的指针合法性
*/
verify(it._ptr - 1, _last);
T *p = _last;
while (p > it._ptr)
{
_allocator.construct(p, *(p-1));
_allocator.destroy(p - 1);
p--;
}
_allocator.construct(p, val);
_last++;
return iterator(this, p);
}
// 自定义vector容器erase方法的实现
iterator erase(iterator it)
{
verify(it._ptr - 1, _last);
T *p = it._ptr;
while (p < _last-1)
{
_allocator.destroy(p);
_allocator.construct(p, *(p + 1));
p++;
}
_allocator.destroy(p);
_last--;
return iterator(this, it._ptr);
}
private:
T *_first; // 指向数组起始的位置
T *_last; // 指向数组中有效元素的后继位置
T *_end; // 指向数组空间的后继位置
Alloc _allocator; // 定义容器的空间配置器对象
// 容器迭代器失效增加代码
struct Iterator_Base
{
Iterator_Base(iterator *c=nullptr, Iterator_Base *n=nullptr)
:_cur(c), _next(n) {}
iterator *_cur;
Iterator_Base *_next;
};
Iterator_Base _head;
void expand() // 容器的二倍扩容
{
int size = _end - _first;
//T *ptmp = new T[2 * size];
T *ptmp = _allocator.allocate(2 * size);
for (int i = 0; i < size; ++i)
{
//ptmp[i] = _first[i];
_allocator.construct(ptmp + i, _first[i]);
}
//delete[]_first;
for (T *p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};
int main()
{
vector<int> vec(200);
for (int i = 0; i < 20 ; ++i)
{
vec.push_back(rand() % 100 + 1);
}
auto it = vec.begin();
while (it != vec.end())
{
if (*it % 2 == 0)
{
// 迭代器失效的问题,第一次调用erase以后,迭代器it就失效了
it = vec.erase(it); // insert(it, val) erase(it)
}
else
{
++it;
}
}
for (int v : vec)
{
cout << v << " ";
}
cout << endl;
#if 0
auto it1 = vec.end();
vec.pop_back(); // verify(_last-1, _last)
auto it2 = vec.end();
cout << (it1 != it2) << endl;
int size = vec.size();
for (int i = 0; i < size; ++i)
{
cout << vec[i] << " ";
}
cout << endl;
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
// foreach
for (int val : vec) // 其底层原理,就是通过容器的迭代器来实现容器遍历的
{
cout << val << " ";
}
cout << endl;
#endif
return 0;
}
#endif
以上是STL——vector容器比较完整的仿写,包括了迭代器的失效原理,迭代器失效,我们会在后面讲到,读者应该会发现vector迭代器的底层多了一个指针,而vector底层多了一个自由链表,他们的作用我们会在下面的迭代器失效部分讲到
迭代器失效问题
什么是迭代器失效
迭代器失效分为一下两种类型
- 当对容器底部插入元素时,size == capicity时,需要扩容,扩容后,容器的所用迭代器均失效,因为,迭代器的底层就是一个指针,而扩容后,容器开辟了新的空间,指针指向的空间被释放,相对应容器的迭代器也就失效了
- 当对容器插入元素或者删除元素后,插入点以及插入点之后,删除点以及删除点之后的迭代器全部失效。
从底层源码理解迭代器失效
在上述vector容器简单实现中,读者应该会发现,迭代器底层不只是封装了一个指针,还有pVec,这个指的是迭代器所迭代的容器对象,还定义了一个链表结构,结构如下:
链表结构储存的是指向已定义迭代器的指针,每当定义一个迭代器的时候就会将迭代器的地址头插入链表中。代码中的verify函数是使迭代器失效的函数,当插入或者删除的时候,会调用verify函数,遍历链表,看链表里的迭代器是否在插入点(或删除点之后),如果在的话,就将迭代器的pVec置为空,读者们应该也会发现在it++等运算符操作的时候会判断pVect是否为空,如果为空,就说明对应的迭代器失效,就不能在进行对应的运算符操作了,迭代器也就因此而失效了
如何处理迭代器失效问题
首先,我们先举两个迭代器失效的例子,再看看如何解决迭代器的失效问题:
1、在删除的时候迭代器失效
int main()
{
vector<int> vec;
for (int i = 1; i <= 10; i++)
{
vec.push_back(i);
}
for (auto it = vec.begin(); it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
it++;
}
else
{
vec.erase(it);
}
}
return 0;
}
以上程序是用迭代起来遍历容器元素按,并删除将奇数删除。
运行后会发现会报错
是因为在调用erase()成员方法后,删除点的以及删除点之后的迭代器均会失效,要解决这个问题,我们得知道erase的具体操作,erase会将删除点的下一个有效迭代器返回,所以,我们只需要对迭代器更新就可以了。如下程序:
int main()
{
vector<int> vec;
for (int i = 1; i <= 10; i++)
{
vec.push_back(i);
}
auto it = vec.begin();
while (it != vec.end())
{
if (*it % 2 == 0)
{
it++;
}
else
{
it = vec.erase(it);
}
}
for (int ch : vec)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
这里需要注意的是我们不能在删除了元素后再对it++,因为删除后返回的就是删除点的下一个元素的有效迭代器。这样就有效的解决了迭代器失效问题。
2、在插入时迭代器失效问题
int main()
{
vector<int> vec;
for (int i = 1; i <= 10; i++)
{
vec.push_back(i);
}
auto it = vec.begin();
for (; it != vec.end(); it++)
{
if(*it % 2 == 0)
{
vec.insert(it+1, *(it) - 1);
}
}
for (int ch : vec)
{
cout << ch << " ";
}
return 0;
}
运行程序后会报错,和上述删除代码错误原因一样
上述代码是用迭代器在偶数后插入比当前小1的数,是因为在插入元素后,插入点的迭代器就失效了,所以不能够进行遍历,程序就崩溃了,insert方法返回的是插入后插入点的有效迭代器,我们只需要对迭代器进行更新即可,修改后的代码如下:
int main()
{
vector<int> vec;
for (int i = 1; i <= 10; i++)
{
vec.push_back(i);
}
auto it = vec.begin();
for (; it != vec.end(); it++)
{
if(*it % 2 == 0)
{
it = vec.insert(it+1, *(it) - 1);
}
}
for (int ch : vec)
{
cout << ch << " ";
}
return 0;
}
运行结果如下:
综上,在vector中插入和删除元素时,我们应该根据需求,对删除和插入操作后的迭代器对原迭代器进行更新即可