输入迭代器与输出迭代器
困难3输入迭代器与输出迭代器:数据流的“单向通道”
想象一下,你站在演唱会的入口,手里拿着一张票。检票员(输入迭代器)一次只能检查一个人的票,检查完这个人就进去了,不能再回头让检票员重新看一眼。检票员也不会倒退回去检查队伍前面的人。这就是输入迭代器的工作方式:只能读取一次,只能向前移动。
再想想打印机里墨水喷头的动作:它只能往纸上喷墨(写入),不能回头擦掉已经打印的字,也不能在同一位置重复打印两次——这就是输出迭代器的工作方式:只能写入一次,只能向前移动。
这两种迭代器是C++ STL迭代器家族中最基础、能力最弱的两种,但也是流式数据处理的核心。它们为算法与数据源(如键盘输入、文件、容器)之间搭起了“单向通行”的桥梁。
输入迭代器:只会“读”的巡游者
生活中的例子
- 自动售货机取饮料:你按下一个按钮(相当于
++it到下一个位置),然后取出饮料(*it读取)。一旦饮料掉下来,你不可能再把同一瓶饮料塞回去重新取。 - 读一条短信:你盯着手机屏幕逐条看消息,看完一条滑动到下一条,不能后退看前一条(除非手动滑回去,但输入迭代器没有这个功能)。
输入迭代器的特点
- 只能读取:通过
*it获取当前元素的值,但不能修改它。 - 单向移动:只能通过
++it或it++向前走,不能后退。 - 单遍扫描:每个元素只能访问一次。一旦迭代器前移,之前的元素就再也无法通过这个迭代器访问了(除非你保存了副本,但副本也必须自己维护)。
- 可以比较相等:能用
==和!=判断两个迭代器是否指向同一个位置,例如用于判断是否到达末尾。 - 不保证可重复解引用:当你有两个不同的输入迭代器指向同一个元素时,它们分别解引用可能会得到不同的值(比如流中的字符被消耗掉一次后就没了)。因此,算法中通常只使用一个输入迭代器,不会保留多个副本进行多次读取。
C++中的典型代表:istream_iterator
istream_iterator 绑定到一个输入流(如 cin、ifstream),每次解引用都会从流中读取一个数据。默认构造的 istream_iterator 表示流结束(类似哨兵)。
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 从键盘读取整数,直到遇到非数字字符或EOF
istream_iterator<int> in_iter(cin); // 输入迭代器,绑定标准输入
istream_iterator<int> eof; // 默认构造:代表流结束
vector<int> nums; // 存储读入的数字
copy(in_iter, eof, back_inserter(nums)); // 将输入复制到vector末尾
cout << "你输入了:";
for (int n : nums) cout << n << " ";
cout << endl;
return 0;
}
代码解释:
istream_iterator<int>(cin)创建了一个可以从cin连续读取整数的迭代器。每解引用一次,就消耗一个整数。istream_iterator<int>()默认构造的迭代器代表“流结束”,常用于作为哨兵。copy(in_iter, eof, back_inserter(nums)):从in_iter开始,逐个读取整数,通过back_inserter写入nums的末尾,直到遇到eof(即输入结束)。- 这个程序运行时,你需要输入一系列整数(例如
5 10 20),然后输入一个非数字字符(如字母q)或按Ctrl+Z(Windows)/Ctrl+D(Linux)结束输入。
Python中模拟输入迭代器
Python没有内置的输入流迭代器类别,但可以用生成器(generator)实现类似单遍读取的效果:
def input_iterator():
"""输入迭代器:从键盘读取整数,一行一个,空行结束"""
while True:
line = input("请输入整数(直接回车结束):")
if line == "":
break
yield int(line) # 每次yield出一个整数,消耗一个输入
# 使用列表推导收集所有输入
data = list(input_iterator())
print("你输入了:", data)
注意:生成器也是单遍的,一旦迭代完毕,不能再重复使用。
输出迭代器:只会“写”的工人
生活中的例子
- 在作业本上抄写生字:你只能一个格一个格地往前写(
++it移到下一个位置),不能回头涂抹已经写过的字(除非用修正带,但输出迭代器不允许)。你也不能在同一个格子里写两次。 - 超市收银台打印购物小票:热敏打印机只能一行一行地打印文字,不能回头擦掉已经打印的内容。每打印一行,就相当于输出迭代器前进一步。
输出迭代器的特点
- 只能写入:通过
*it = value将值写入当前位置。 - 单向移动:只能前移(
++it),不能后退或重新读写之前的位置。 - 不支持比较:不能使用
==或!=比较两个输出迭代器是否相等。你通常不会需要比较它们,因为它们只是“写入的通道”。 - 单遍写入:每个位置只能写入一次,多次写入同一位置会导致未定义行为(比如覆盖了有意义的数据,或者插入迭代器反复插入)。
- 不能读取:解引用输出迭代器只能用于赋值左侧,不能出现在赋值右侧或作为表达式读取。
C++中的典型代表:ostream_iterator
ostream_iterator 绑定到一个输出流(如 cout、ofstream),每次赋值都会将数据写入流并自动添加分隔符。
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main() {
vector<int> nums = {3, 1, 4, 1, 5};
// 创建一个输出迭代器,绑定到cout,元素之间用逗号加空格分隔
ostream_iterator<int> out_iter(cout, ", ");
// 将nums中的所有元素写入输出迭代器
copy(nums.begin(), nums.end(), out_iter);
// 输出:3, 1, 4, 1, 5,
cout << endl;
// 也可以在循环中手动写入
ostream_iterator<int> out_iter2(cout, " ");
for (int n : nums) {
*out_iter2 = n; // 写入当前位置
++out_iter2; // 前移到下一个位置
}
cout << endl; // 输出:3 1 4 1 5
return 0;
}
代码解释:
ostream_iterator<int>(cout, ", "):创建输出迭代器,每次写入一个整数后自动输出", "。- 在循环中,
*out_iter2 = n将n写入,++out_iter2移动到下一个输出槽位。你可以连续写,但注意不要重复写同一个位置。
常见的输出迭代器变体:back_inserter
back_inserter 其实也是一个输出迭代器,但它不是绑定到流,而是绑定到一个容器(如 vector)上。每次写入时,它会调用容器的 push_back 方法插入元素。
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main() {
vector<int> source = {10, 20, 30};
vector<int> dest; // 空目标容器
// back_inserter(dest) 是一个输出迭代器,每次写入就在dest末尾插入
copy(source.begin(), source.end(), back_inserter(dest));
cout << "dest中有:";
for (int x : dest) cout << x << " "; // 输出:10 20 30
cout << endl;
return 0;
}
Python中模拟输出迭代器
Python没有直接对应的类型,但我们可以自己写一个简单的类,模拟“只能写入、不能读取”的行为:
class OstreamIterator:
"""模仿C++输出迭代器,将元素写入列表或打印到屏幕"""
def __init__(self, container=None, separator=" "):
self.container = container # 如果是None,则直接print
self.sep = separator
self.first = True
def write(self, value):
if self.container is None:
# 输出到屏幕
if self.first:
print(value, end="")
self.first = False
else:
print(self.sep + str(value), end="")
else:
# 写入容器(列表)
self.container.append(value)
# 用法1:输出到屏幕
out1 = OstreamIterator(separator=", ")
out1.write(10)
out1.write(20) # 屏幕上打印:10, 20
# 用法2:写入列表
result = []
out2 = OstreamIterator(container=result, separator=", ")
out2.write(100)
out2.write(200)
print("\nresult列表:", result) # 输出:[100, 200]
不过,在Python实际开发中,直接用列表推导或循环赋值更常见。这里只是为了展示概念。
新手容易犯的错误
-
对输入迭代器多次解引用
istream_iterator<int> it(cin); int a = *it; // 正确,读取第一个整数 int b = *it; // 错误!第一个整数已经被消耗,再次解引用会导致未定义行为正确做法:每次解引用前,确保迭代器仍指向有效元素,且不要重复使用同一个迭代器读取同一个元素。
-
试图比较输出迭代器
ostream_iterator<int> it1(cout); ostream_iterator<int> it2(cout); if (it1 == it2) { ... } // 编译错误!输出迭代器不支持==输出迭代器一般不用于比较,也不需要比较。算法中只需提供其写入目标即可。
-
认为输出迭代器可以读取
ostream_iterator<int> it(cout); int val = *it; // 编译错误!输出迭代器的*it只能用于赋值,不能用于读取 -
在需要多遍扫描的算法中使用输入/输出迭代器
例如,std::sort要求随机访问迭代器(可以多次读取、随机移动),而输入迭代器是单遍的。如果传入istream_iterator,编译会报错或导致未定义行为。 -
忘记检查流结束
对于istream_iterator,只有当流到达末尾或发生错误时,才会等于哨兵迭代器。如果用户只输入了部分数据但未停止,程序会一直等待。因此在交互式程序中,最好提示如何结束输入。
完整可运行示例:从文件读取整数并输出到另一个文件
下面是一个综合示例,演示输入/输出迭代器与文件流配合使用。
#include <iostream>
#include <fstream>
#include <iterator>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 1. 打开输入文件 "in.txt",假设里面有多行整数
ifstream ifs("in.txt"); // 输入文件流
if (!ifs) {
cerr << "无法打开输入文件!" << endl;
return 1;
}
istream_iterator<int> file_in(ifs); // 输入迭代器绑定文件
istream_iterator<int> eof; // 文件结束哨兵
vector<int> numbers; // 存储读取的数字
copy(file_in, eof, back_inserter(numbers)); // 读入到vector
// 2. 对数字进行处理:每个数加10
for (int &n : numbers) n += 10;
// 3. 打开输出文件 "out.txt"
ofstream ofs("out.txt");
if (!ofs) {
cerr << "无法打开输出文件!" << endl;
return 1;
}
ostream_iterator<int> file_out(ofs, "\n"); // 输出迭代器,每行一个数字
copy(numbers.begin(), numbers.end(), file_out); // 写入文件
cout << "处理完成,结果已写入 out.txt" << endl;
return 0;
}
准备输入文件 in.txt,内容例如:
1
2
3
4
5
运行后,out.txt 中会得到:
11
12
13
14
15
代码说明:
istream_iterator<int>(ifs)和istream_iterator<int>()可以无缝适配文件流,就像适配cin一样。back_inserter(numbers)是一个输出迭代器,每次写入自动扩展vector。ostream_iterator<int>(ofs, "\n")将结果写入输出文件,每个数字后换行。
输入/输出迭代器与其他迭代器的对比
| 操作 | 输入迭代器 | 输出迭代器 | 前向迭代器 | 双向迭代器 | 随机访问迭代器 |
|---|---|---|---|---|---|
读取 *it | ✅ | ❌ | ✅ | ✅ | ✅ |
写入 *it = val | ❌ | ✅ | ✅ | ✅ | ✅ |
前移 ++it | ✅ | ✅ | ✅ | ✅ | ✅ |
后移 --it | ❌ | ❌ | ❌ | ✅ | ✅ |
比较相等 == | ✅ | ❌ | ✅ | ✅ | ✅ |
| 多遍扫描 | ❌ | ❌ | ✅ | ✅ | ✅ |
随机访问 it[n] | ❌ | ❌ | ❌ | ❌ | ✅ |
输入/输出迭代器是最基础的,能力弱但足够用于流式数据。很多标准算法(如 copy, find, count)要求源端至少是输入迭代器,目标端至少是输出迭代器。
总结要点
- 输入迭代器:只读、单向、单遍。典型代表
istream_iterator,常与copy等算法配合从流或容器中读取数据。 - 输出迭代器:只写、单向、单遍。典型代表
ostream_iterator和back_inserter,常作为算法的目标。 - 单遍特性 是两者最重要的限制:你不能指望重新读取已经用过的元素,也不能在同一个位置写入两次。
- 组合使用:算法如
copy(src_begin, src_end, dest_begin)自动适应输入/输出迭代器,将数据从一端传输到另一端,不关心具体容器类型。 - Python 中没有严格分类,但可以用生成器模拟输入迭代器的单遍读取,用自定义类模拟输出迭代器。
理解输入和输出迭代器是掌握 STL 算法的基础。下一节,我们将学习能力更强的 前向迭代器、双向迭代器 和 随机访问迭代器——它们可以多遍扫描,甚至支持随机移动,能胜任更复杂的任务(如排序、反转)。
例题精讲
关于输入迭代器(Input Iterator),以下说法正确的是:
输入迭代器(Input Iterator)可以用于修改它所指向的元素的值。
要将字符串流中的单词逐个输出到标准输出,可以使用copy算法配合___迭代器。代码:copy(istream_iterator<string>(cin), istream_iterator<string>(), ___ );下列哪个迭代器类型是输出迭代器(Output Iterator)的典型例子?
输出迭代器(Output Iterator)支持通过解引用操作符(*)来读取元素。