一、什么是迭代器
在STL的设计理念中,将存储数据的类型(容器)和操作容器的动作(算法)二者彼此独立开来,这就导致我们无法直接通过算法来去操作存储在容器中的数据,而Iteratror的设计,就是为了提供一种统一的访问容器中元素的方式,无论容器的内部结构如何,通过迭代器,我们的算法就能够去操作相应的数据。以标准库find()为例,我们只需要告诉find查找元素的起始位置和终点位置,以及一个目标值,而不需要告诉它容器的类型,它就可以帮我们在该范围内找是否有目标值。在STL六大部件中,每个组件都是一个类模板,因此,迭代器也是一个类模板。
template <typename Inputerator, class T>
Inputerator find(Inputerator first, Inputerator last, const T& value) {
while (first != last && *first != value) {
++first;
}
return first;
}
二、迭代器是一种smart pointer
对于指针,我们通常会有两种常用的操作,一种是解引用(operator*)和成员访问(operator->)而迭代器恰恰重载了这两个操作符,因此迭代器在行为上可以看作是一个指针。此外迭代器还重载了++,--,==,!=,-运算符,以便算法在工作中可以对容器中的数据进行这些操作。
2.1.1 解引用和成员访问运算符
在下面这段代码中,我们以vector容器为例,测试迭代器的解引用运算符和成员访问运算符,我们在容器中存储A对象,然后通过不同的方式对容器中的对象进行访问,分别通过对容器取值,对指向容器的迭代器进行成员访问和解引用三种方式遍历容器,通过代码的运行结果可以发现,三种方式都可以对容器中的元素进行遍历。通过对迭代器进行解引用操作,我们会得到一个A对象,而对迭代器进行成员访问操作,我们会得到一个指向A的指针A*,我们也可以进行这样的操作:it->get_currency(),它会拿到m_currency这个属性,这样一来,我们会发现,迭代器的解引用和成员访问和原始指针的解引用和成员访问结果是一样的,所以说迭代器在行为上可以看作是一个指针。
#include <iostream>
#include <vector>
#include <initializer_list>
using namespace std;
namespace jz
{
class A
{
public:
A() = default;
A(double rate, string currency) :
m_rate(rate), m_currency(currency) {}
double get_rate() { return m_rate; }
string get_currency() { return m_currency; }
friend ostream& operator<<(ostream& os, A& a) {
os << a.m_currency << " " << a.m_rate << endl;
return os;
}
friend ostream& operator<<(ostream& os, A* a) {
os << a->m_currency << " " << a->m_rate << endl;
return os;
}
private:
double m_rate;
string m_currency;
};
void test() {
A a1(7.2606, "USD/CNY");
A a2(146.2300, "USD/JPY");
vector<A> v{a1, a2};
// 不使用迭代器访问A
cout << "不使用迭代器访问A----------" << endl;
for (auto& it : v) {
cout << it;
}
// 使用迭代器访问A
cout << "使用迭代器访问A------------" << endl;
for (auto it = v.begin(); it != v.end(); ++it) {
auto ret = it.operator->();
cout << *ret;
cout << ret;
}
cout << "----------------------------" << endl;
for (auto it = v.begin(); it != v.end(); ++it) {
auto ret = *it;
cout << &ret;
cout << ret;
}
}
} // namespace jz
2.2.2 迭代器的其他操作
迭代器被称为是一种smart pointer,因此,它不仅仅是可以解引用和成员访问,同时也可以做一些算术操作以及逻辑操作。以下是一个简单的例子。
cout << "以下是对迭代器的一些其他操作" << endl;
vector<A>::iterator itBegin = v.begin(); // 取容器第一个元素的迭代器
vector<A>::iterator itEnd = v.end(); // 取容器的尾后迭代器
vector<A>::value_type aa(0.8559, "EUR/GBP"); // A
vector<A>::reference ra = aa; // A&
vector<A>::pointer pa = &aa; // A*
vector<A>::difference_type dt; // ptrdiff_t
vector<A>::size_type st; // size_t
cout << *(itBegin++); // 对迭代器进行后++操作
cout << *(++itBegin); // 对迭代器进行前++操作
cout << (itBegin == itEnd) << endl; // 判断两个迭代器是否相等
cout << (itBegin < itEnd) << endl; // 判断两个迭代器的大小
cout << (itEnd - itBegin) << endl; // 计算两个迭代器之间的距离
三、迭代器的种类(category)
根据迭代器的移动特性和实施操作,迭代器可以分为以下五种:
input iterator:这种迭代器所指的对象,不允许外界改变。只能对它进行读操作。
output iterator:只能对它进行写操作。
forward iterator:单向、可读、可写迭代器,对于这种迭代器,我们可以对它进行读写操作,前进操作。
bidirectional iterator:双向、可读、可写迭代器,对于这种迭代器,我们可以对它进行读写操作,不仅可以前向移动,还可以进行后退操作。
random access iterator:随机访问迭代器,这种迭代器的操作权限是最高的,不仅读写、前进、后退、我们还可以进行+n,-n,[]操作。
以下代码来自STL源码剖析:
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
四、Traits编程技巧
我们可以使用iterator_traits这个模板类,来了解到STL各个容器使用的迭代器类型,在该模板类中,我们定义了迭代器的五种型别,我们可以通过访问这个五种型别,来知道我们迭代器所对应的型别是什么。以下是一个测试各个容器迭代器类型的例子。在代码中,我们通过display,将容器的迭代器传递进去,然后通过iterator_traits萃取出迭代器的类型,之后同调用_display这个函数,打印出迭代器的类型。
using namespace std;
namespace jz
{
void _display(input_iterator_tag) {
cout << "input_iterator_tag" << endl;
}
void _display(output_iterator_tag) {
cout << "output_iterator_tag" << endl;
}
void _display(forward_iterator_tag) {
cout << "forward_iterator_tag" << endl;
}
void _display(bidirectional_iterator_tag) {
cout << "bidirectional_iterator_tag" << endl;
}
void _display(random_access_iterator_tag) {
cout << "random_access_iterator_tag" << endl;
}
template <typename T>
void display(T Itr) {
typename iterator_traits<T>::iterator_category cagy;
_display(cagy);
cout << "typeid(Itr).name()=" << typeid(Itr).name() << endl;
}
void test() {
display(array<int, 1>::iterator());
display(vector<int>::iterator());
display(forward_list<int>::iterator());
display(list<int>::iterator());
display(deque<int>::iterator());
display(set<int>::iterator());
display(map<int, int>::iterator());
display(unordered_map<int, int>::iterator());
display(unordered_set<int>::iterator());
display(istream_iterator<int>());
display(ostream_iterator<int>(cout, ""));
}
} // namespace jz