CC++ & Algorithm

输入迭代器与输出迭代器

困难3
语言版本:通用
概述:通过“演唱会排队入场”和“打印机输出”的例子,讲解输入输出迭代器的特点、用法及C++和Python代码示例。

输入迭代器与输出迭代器:数据流的“单向通道”

想象一下,你站在演唱会的入口,手里拿着一张票。检票员(输入迭代器)一次只能检查一个人的票,检查完这个人就进去了,不能再回头让检票员重新看一眼。检票员也不会倒退回去检查队伍前面的人。这就是输入迭代器的工作方式:只能读取一次,只能向前移动

再想想打印机里墨水喷头的动作:它只能往纸上喷墨(写入),不能回头擦掉已经打印的字,也不能在同一位置重复打印两次——这就是输出迭代器的工作方式:只能写入一次,只能向前移动

这两种迭代器是C++ STL迭代器家族中最基础、能力最弱的两种,但也是流式数据处理的核心。它们为算法与数据源(如键盘输入、文件、容器)之间搭起了“单向通行”的桥梁。


输入迭代器:只会“读”的巡游者

生活中的例子

  • 自动售货机取饮料:你按下一个按钮(相当于 ++it 到下一个位置),然后取出饮料( *it 读取)。一旦饮料掉下来,你不可能再把同一瓶饮料塞回去重新取。
  • 读一条短信:你盯着手机屏幕逐条看消息,看完一条滑动到下一条,不能后退看前一条(除非手动滑回去,但输入迭代器没有这个功能)。

输入迭代器的特点

  • 只能读取:通过 *it 获取当前元素的值,但不能修改它。
  • 单向移动:只能通过 ++itit++ 向前走,不能后退。
  • 单遍扫描:每个元素只能访问一次。一旦迭代器前移,之前的元素就再也无法通过这个迭代器访问了(除非你保存了副本,但副本也必须自己维护)。
  • 可以比较相等:能用 ==!= 判断两个迭代器是否指向同一个位置,例如用于判断是否到达末尾。
  • 不保证可重复解引用:当你有两个不同的输入迭代器指向同一个元素时,它们分别解引用可能会得到不同的值(比如流中的字符被消耗掉一次后就没了)。因此,算法中通常只使用一个输入迭代器,不会保留多个副本进行多次读取。

C++中的典型代表:istream_iterator

istream_iterator 绑定到一个输入流(如 cinifstream),每次解引用都会从流中读取一个数据。默认构造的 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 绑定到一个输出流(如 coutofstream),每次赋值都会将数据写入流并自动添加分隔符。

#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 = nn 写入,++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实际开发中,直接用列表推导或循环赋值更常见。这里只是为了展示概念。


新手容易犯的错误

  1. 对输入迭代器多次解引用

    istream_iterator<int> it(cin);
    int a = *it;   // 正确,读取第一个整数
    int b = *it;   // 错误!第一个整数已经被消耗,再次解引用会导致未定义行为
    

    正确做法:每次解引用前,确保迭代器仍指向有效元素,且不要重复使用同一个迭代器读取同一个元素。

  2. 试图比较输出迭代器

    ostream_iterator<int> it1(cout);
    ostream_iterator<int> it2(cout);
    if (it1 == it2) { ... }   // 编译错误!输出迭代器不支持==
    

    输出迭代器一般不用于比较,也不需要比较。算法中只需提供其写入目标即可。

  3. 认为输出迭代器可以读取

    ostream_iterator<int> it(cout);
    int val = *it;   // 编译错误!输出迭代器的*it只能用于赋值,不能用于读取
    
  4. 在需要多遍扫描的算法中使用输入/输出迭代器
    例如,std::sort 要求随机访问迭代器(可以多次读取、随机移动),而输入迭代器是单遍的。如果传入 istream_iterator,编译会报错或导致未定义行为。

  5. 忘记检查流结束
    对于 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)要求源端至少是输入迭代器,目标端至少是输出迭代器。


总结要点

  1. 输入迭代器:只读、单向、单遍。典型代表 istream_iterator,常与 copy 等算法配合从流或容器中读取数据。
  2. 输出迭代器:只写、单向、单遍。典型代表 ostream_iteratorback_inserter,常作为算法的目标。
  3. 单遍特性 是两者最重要的限制:你不能指望重新读取已经用过的元素,也不能在同一个位置写入两次。
  4. 组合使用:算法如 copy(src_begin, src_end, dest_begin) 自动适应输入/输出迭代器,将数据从一端传输到另一端,不关心具体容器类型。
  5. Python 中没有严格分类,但可以用生成器模拟输入迭代器的单遍读取,用自定义类模拟输出迭代器。

理解输入和输出迭代器是掌握 STL 算法的基础。下一节,我们将学习能力更强的 前向迭代器双向迭代器随机访问迭代器——它们可以多遍扫描,甚至支持随机移动,能胜任更复杂的任务(如排序、反转)。

例题精讲

1单选题

关于输入迭代器(Input Iterator),以下说法正确的是:

A可以多次对同一元素进行解引用读取
B支持--操作符以实现反向遍历
C只能用于向容器写入数据
D只能保证单向遍历,且某些输入迭代器可能不支持多次解引用
2判断题

输入迭代器(Input Iterator)可以用于修改它所指向的元素的值。

3填空题
要将字符串流中的单词逐个输出到标准输出,可以使用copy算法配合___迭代器。代码:copy(istream_iterator<string>(cin), istream_iterator<string>(), ___ );
4单选题

下列哪个迭代器类型是输出迭代器(Output Iterator)的典型例子?

Aistream_iterator
Bostream_iterator
Cvector::iterator
Dlist::iterator
5判断题

输出迭代器(Output Iterator)支持通过解引用操作符(*)来读取元素。