迭代器
迭代器类似于指针类型,也提供了对对象的间接访问。
就迭代器而言,其对象是容器中的元素或 string 对象中的字符。
获取迭代器
容器的迭代器类型
使用作用域运算符来说明我们希望使用的类型成员;例:string::iterator iter;
类型别名 | 功能 |
---|---|
iterator | 此容器类型的迭代器 |
const_iterator | 可以读取元素,但不能修改元素的迭代器类型 |
diffreence_type | 带符号整数类型,足够保存两个迭代器之间的距离 |
begin 和 end 成员
和指针不同的是,获取迭代器不适用取地址运算符,有迭代器的类型,同时拥有返回迭代器
的成员函数。
成员函数 begin 生成指向容器中的第一个元素的迭代器。
成员函数 end 生成指向容器中的尾元素之后位置的迭代器(简称尾后迭代器)。
/*获取迭代器,使用 auto 推断迭代器的类型*/
string str{ "Hello World" };
auto iter_b = str.begin();
auto iter_e = str.end();
begin 和 end 有多个版本:带 r 的版本返回反向迭代器,以 c 开头的版本则返回 const 迭代器。
可以将一个普通的 iterator 转换为对应的 const_iterator,但反之不行。
以 c 开头的版本是C++新标准引入的,用以支持 auto 与 begin 和 end 函数结合使用。
迭代器运算
解引用
可以通过解引用迭代器来获取它所指向的元素。
string str{ "Hello World" };
auto iter = str.begin();
cout << *iter << endl; //输出 H
试图解引用一个非法的迭代器或者尾后迭代器结果都是未定义的。
算数运算
迭代器加上或减去整数
迭代器加上(或减去)一个整数值扔得一个迭代器,迭代器指示的新位置与原来相比向前移动了若干个元素。
string str{ "Hello World" };
auto iter = str.begin();
iter = iter + 4;
cout << *iter << endl; // 输出 o
cout << *(iter - 3) << endl; // 输出 e
迭代器支持加法(或减法)的复合赋值语句
string str{ "Hello World" };
auto iter = str.begin();
iter += 4;
cout << *iter << endl; // 输出 o
iter -= 3;
cout << *iter << endl; // 输出 e
迭代器支持递增(或递减运算符),表示迭代器指向下一个元素。
string str{ "Hello World" };
auto iter = str.begin();
/* 将迭代器向前移动一个元素,然后输出移动后的值 */
cout << *++iter << endl; // 输出 e
/* 将迭代器向后移动一个元素,然后输出移动后的值 */
cout << *--iter << endl; // 输出 H
两个迭代器可以相减
如果两个迭代器指向同一个容器,则两个迭代器相减的结果是它们之间的距离。
string str{ "Hello World" };
auto iter = str.begin();
auto iter_b = iter + 1;
auto iter_e = iter + 4;
cout << (iter_e - iter_b) << endl; //输出 3
两个迭代器可以比较
如果两个迭代器指向同一个容器,则可以进行比较;指向前面元素的迭代器小于指向后面元素的迭代器。
string str{ "Hello World" };
auto iter = str.begin();
auto iter_b = iter + 1;
auto iter_e = iter + 4;
cout << (iter_b < iter_e) << endl; //输出 1
如果两个迭代器相等,则两个迭代器指向同一个元素,或者它们是同一个容器的尾后迭代器。
使用迭代器遍历容器
string str{ "Hello World" };
auto it_c = str.cbegin();
for (auto iter = str.begin(); iter < it_c + str.size(); iter += 1)
{
cout << *iter;
}
迭代器范围
一个迭代器范围(iterator range)由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者尾元素之后的位置(one past the last element)。
迭代器范围也被称为左闭合区间(left-inclusive interval),其标准数学描述为:[begin end)
标准库使用左闭合范围是因为这种范围有三种方便的性质。
假定 begin 和 end 构成一个合法的迭代器范围,则
- 如果 begin 和 end 相等,则范围为空。
- 如果 begin 和 end 不相等,则范围至少包含一个元素,且 begin 指向该范围中的第一个元素。
- 我们可以对 begin 递增若干次,使得
begin == end
。
使用迭代器遍历容器
string str{ "Hello World" };
auto iter = str.begin();
while ( iter != str.cend() )
{
cout << *iter++ << ' ';
}
使用!=
而不是<
判断是否到达尾序列;部分原因是,只有随机访问迭代器才支持<
运算符。
迭代器适配器
在<iterator>
中,标准库提供了迭代器适配器,能从一个给定的迭代器类型生成有用的相关迭代器类型:
类型别名 | 功能 |
---|---|
reverse_iterator | 反向遍历 |
back_insert_iterator | 在尾部插入 |
front_insert_iterator | 在头部插入 |
insert_iterator | 在任意位置插入 |
move_iterator | 移动而不是拷贝 |
raw_storage_iterator | 写入未初始化的存储空间 |
反向迭代器
除了 forward_list 之外的标准库容器都有反向迭代器。
反向迭代器就是在容器中从尾元素反向移动的迭代器。
成员函数 rbegin 返回指向容器尾元素的迭代器。
成员函数 rend 返回指向容器首元素之前位置的迭代器。
反向迭代器迭代器也有 const 版本,即 crbegin 和 crend。
使用反向迭代器逆序遍历容器
deque <char> D{ 'A','B','C','D','E' };
auto it = D.crbegin();
while (it != D.crend())
{
cout << *it++ << ' ';
}
插入迭代器
插入迭代器是一种迭代器适配器,它接受一个容器,生成一个迭代器,能实现向给定容器添加元素。
当我们通过一个插入迭代器进行赋值时,该迭代器调用容器操作来给定容器的指定位置插入一个元素。
插入迭代器有三种类型,差异在于元素插入的位置:
函数 back_inserter 用 push_back 在序列尾元素之后插入新值。
deque <char> D{ 'F','G','H' };
auto it = back_inserter(D);
/*尾后插入字母 A B C D E */
for (char i = 'A'; i < 'F'; ++i)
{
*it = i; //等价于 D.push_back( i );
}
/* 队列内的元素变为:F G H A B C D E */
函数 front_inserter 用 push_front 在序列首元素之前插入新值。
deque <char> D{ 'F','G','H' };
auto it = front_inserter(D);
/*首前插入字母 A B C D E */
for (char i = 'A'; i < 'F'; ++i)
{
*it = i; //等价于 D.push_front( i );
}
/* 队列内的元素变为:E D C B A F G H */
函数 inserter 用 insert 在指向的元素之前插入新值。
此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器;元素被插入到给定迭代器所表示的元素之前。
deque <char> D{ 'F','G','H' };
auto it = inserter ( D ,D.begin() );
for (char i = 'A'; i < 'F'; ++i)
{
*it = i; //等价于 it = D.insert(it,i); ++it; 其中,it = D.begin();
}
/* 队列内的元素变为:A B C D E F G H */
移动迭代器
通过移动迭代器读取元素时会移动元素而非拷贝元素。
调用标准库的 make_move_inserter 函数将一个普通迭代器转换为一个移动迭代器。
mp = make_move_inserter(p);// mp 指向 p 所指向的元素;p 必须是一个输入迭代器
iostream 迭代器
虽然 iostream 类型不是容器,但标准库定义了可以用于这些 IO 类型对象的迭代器。
当创建一个流迭代器时,必须指定迭代器将要读写的对象类型。
istream_iterator 读取输入流
deque <char> D;
/*in_iter从输入流 cin 读取类型为 char 的值*/
istream_iterator <char> in_iter( cin );
istream_iterator <char> eof; //尾后迭代器
while (in_iter != eof)
{
D.push_back( *in_iter++ ); //返回从流中读取的值,然后从流中读取下一个值
}
ostream_iterator 向一个输出流写数据
我们可以提供一个(可选的)第二参数,它是一个字符串字面值,在输出每个元素后都会打印此字符串。
使用
deque <char> D{ 'A','B','C','D','E' };
ostream_iterator <char> out_iter (cout, " ");
for (auto i : D)
out_iter = i; //等价于cout << i << ' ';
范围访问函数
在<iterator>
中,标准库为容器提供了非成员版本的 begin() 和 end() 函数。
int arr[] = { 1,3,5,7,9 };
for (auto it = begin(arr); it < end(arr); ++it)
{
cout << *it << ' ';
}
泛型算法结构
任何算法的最基本的特征是它要求其迭代器提供哪些操作。
算法所要求的迭代器操作可以分为 5 个迭代器类别:
迭代器类别 | 特点 |
---|---|
输入迭代器 | 只读,不写;单遍扫描,只能递增 |
输出迭代器 | 只写,不读;单遍扫描,只能递增 |
前向迭代器 | 可读写;多遍扫描,只能递增 |
双向迭代器 | 可读写;多遍扫描,可递增递减 |
随机访问迭代器 | 可读写;多遍扫描,支持全部迭代器运算 |