信奥竞赛

系列

剑指信奥 | C++ 之 STL - iterator(下)

剑指信奥 | C++ 之 STL - 迭代器(下)

从弱到强的操作符

迭代器的使用容器

上一次课中,我们学习了迭代器的原理和基本作用,了解了迭代器按定义方式进行的分类。

迭代器是为容器提供了统一访问方式的接口,我们之前学习了很多 STL 的容器模版类,是不是所有的容器都可以适用迭代器呢?

我们看一下代码示例:

#include <iostream>
#include <vector>

using namespace std;

int main() {

vector<int> container{1, 2, 3};

// 迭代器的定义
vector<int>::iterator i;

// for 循环输出
cout << "--- for ---" << endl;
for (i = container.begin(); i != container.end(); i++) {
cout << *i << "\t";
}

return 0;
}

/*
output:
--- for ---
1 2 3
*/

这是我们上次课做过的关于 vector 的迭代器的使用,但并不意味着所有的容器都可以。

比如,我们使用容器 stack,仿照上面的方式定义它的迭代器:

stack<int>::iterator i;

会报编译错误,因为 stack 不适用于迭代器。

C++ STL 容器模版类中,适用于迭代器的有以下类型:

  • vector
  • deque
  • list
  • map
  • multimap
  • set
  • multiset

以下容器模版类不适用于迭代器:

  • stack
  • queue
  • priority_queue

迭代器的功能分类

不同类型的迭代器,功能上的强弱也有所不同。

什么是功能上的强弱?就是迭代器所支持的操作种类的多与少,我们看一下代码示例:

#include <iostream>
#include <vector>

using namespace std;

int main() {

vector<int> container{1, 2, 3};

vector<int>::iterator i;

// 注意循环条件:i < container.end()
for (i = container.begin(); i < container.end(); i++) {
cout << *i << "\t";
}

return 0;
}

/*
output:
1 2 3
*/

还是之前的例子,需要注意的是,在 for 循环中,我们的循环条件是:

i < container.end()

我们换一下容器,继续看:

#include <iostream>
#include <list>

using namespace std;

int main() {

list<int> container{1, 2, 3};

list<int>::iterator i;

// 注意循环条件:i != container.end()
for (i = container.begin(); i != container.end(); i++) {
cout << *i << "\t";
}

return 0;
}

/*
output:
1 2 3
*/

这里使用的是 list,再来注意循环条件:

i != container.end()

如果在此处使用 < 进行判断是会报编译错误的,而对于 vector 容器,使用 <!= 进行判断都是可以的,这就体现了不同容器使用迭代器功能上的强弱。

所以,按照功能上的强弱,迭代器可以划分为如下的类别:

  1. 输入迭代器 input iterator
  2. 输出迭代器 output iterator
  3. 前向迭代器 forward iterator
  4. 双向迭代器 bidirectional iterator
  5. 随机访问迭代器 random access iterator

这五种类别的迭代器各自都支持哪些运算呢,我们分别看一下:

  1. 输入迭代器

输入迭代器用于从容器中读取元素,它支持的的操作有:

  • 访问元素或元素的成员 *A A -> member
  • 访问下一个元素 A++ ++A
  • 判断相等或不等 A == B A != B

代码示例:

#include <iostream>
#include <vector>

using namespace std;

int main() {

vector<int> vi = {1, 2, 3};

// 迭代器声明
vector<int>::iterator i;

for (i = vi.begin(); i != vi.end(); ++i) {
// 使用迭代器访问元素
cout << (*i) << "\t";

}

return 0;
}

/*
output
1 2 3
*/

这段代码就是我们使用输入迭代器遍历元素的示例。

  1. 输出迭代器

输出迭代器除了具备输入迭代器的操作之外,还可以为元素赋值:

  • 为元素或元素成员赋值 *A = n A -> m = n

代码示例:

#include<iostream>
#include<vector>

using namespace std;

int main() {

vector<int> vi = {1, 2, 3};

// 迭代器声明
vector<int>::iterator i;

for (i = vi.begin(); i != vi.end(); ++i) {
// 使用迭代器为元素赋值
*i = 1;
}

// 所有元素都已经改变为 1

return 0;
}

/*
output

*/

在上面的代码中,我们使用输出迭代器为元素赋值。

  1. 前向迭代器

前向迭代器同时具备了输入迭代器和输出迭代器的功能,既可以访问元素,又可以修改元素。

代码示例:

#include<iostream>
#include<vector>

using namespace std;

int main() {

vector<int> vi = {1, 2, 3};

// 迭代器声明
vector<int>::iterator i;

for (i = vi.begin(); i != vi.end(); ++i) {
// 使用迭代器为元素赋值
*i = 1;
}

for (i = vi.begin(); i != vi.end(); ++i) {
// 使用迭代器访问元素
cout << (*i) << "\t";
}

return 0;
}

/*
output
1 1 1
*/

上述代码中,我们既可以为元素赋值,又可以访问元素,是一个前向迭代器的示例。

  1. 双向迭代器

双向迭代器,顾名思义,就是在前向迭代器的基础上更近一步,可以实现自减运算

  • 自减运算 A-- --A

代码示例:

#include<iostream>
#include<vector>

using namespace std;

int main() {

vector<int> vi = {1, 2, 3};

// 迭代器声明
vector<int>::iterator i;

for (i = vi.end(); i != vi.begin(); --i) {
// 使用迭代器访问元素
if (i != vi.end()) {
cout << (*i) << "\t";
}
}

cout << *i;

return 0;
}

/*
output
3 2 1
*/

上述代码使用的循环中,迭代器是进行的自减操作,是双向迭代器的示例。

  1. 随机访问迭代器

这是所有迭代器中功能最强的一种,随机访问迭代器在双向迭代器的基础上,可以随机访问任意元素,它所支持的操作有:

  • 比较大小 A < B A > B A <= B A >= B
  • 计算距离 A - B
  • 算术运算 A + n B - n
  • 二元赋值 A += n B -= n
  • 随机访问 A[n]

代码示例:

#include<iostream>
#include<vector>

using namespace std;

int main() {

vector<int> v1 = {1, 2, 3};

// 声明迭代器 1
vector<int>::iterator i1;

// 声明迭代器 2
vector<int>::iterator i2;

// i1 指向 begin
i1 = v1.begin();

// i2 指向 end
i2 = v1.end();

// 比较大小
if (i1 < i2) {
cout << "i1 < i2" << endl;
}

// 计算距离
int distance = i2 - i1;

cout << "distance = " << distance << endl;

// 算术运算
i1 = i1 + 1;

cout << "i1 + 1 = " << *i1 << endl;

// 二元赋值
i2 -= 2;

cout << "i2 -= 2 = " << *i2 << endl;

// 随机访问
cout << "i1[1] = " << i1[1] << endl;

return 0;
}

/*
output
i1 < i2
distance = 3
i1 + 1 = 2
i2 -= 2 = 2
i1[1] = 3
*/

上述代码展示了一个随机访问迭代器的多种运算。

迭代器的分类汇总

最后,我们来看一下我们学过的各种容器类都适用于哪一类迭代器:

容器迭代器功能
vector随机访问迭代器
deque随机访问迭代器
list双向迭代器
set / multiset双向迭代器
map / multimap双向迭代器
stack不支持迭代器
queue不支持迭代器
priority_queue不支持迭代器

我们发现,只有 vectordeque 是支持随机访问迭代器的,所以我们上面的示例都是用的 vector,而 listset  和 map 都是双向迭代器。