输入迭代器是"只能往外读"的迭代器:*it 能取值、++it 能前进,但不能写(*it = x 不合法)、只能走一遍。它最实用的身份是 istream_iterator ——把"从输入流读数据"变成迭代器,于是 vector<int> v(in, eof); 一句就能把一个输入流读成一个容器,while (cin >> x) v.push_back(x); 那套循环就退休了。
一句话记两者的分工:输入迭代器是"数据的来路",输出迭代器是"数据的去路" ——中间夹着算法,这就是 STL 的"流水线"。
上一节(13.4.1)讲了"只能写"的输出迭代器,这一节讲它对称的另一半:"只能读"的输入迭代器。
- 13.4.1 的 Demo ④ 已经预告过它(istream_iterator 读 istringstream),这一节正式讲;
- 它和 13.3 那些"只读一遍"的算法天然匹配:find、count、accumulate 只需要"能读、能 ++" ——所以它们能直接作用在"从输入流读来的数据"上,数据不用先全部装进容器;
- 往回看 2.5.2 C++语言中的流:那里讲的是 cin / cout 这个"流"的概念,这一节把它接到了 STL 上**。
一、它和输出迭代器正好相反
| 输出迭代器(13.4.1) | 输入迭代器(本节) | |
|---|---|---|
| 读写 | 只能写(*it = x) |
只能读(x = *it) |
| 前进 | 只能 ++ |
只能 ++ |
| 遍数 | 单遍 | 单遍 |
| 典型 | ostream_iterator、back_inserter |
istream_iterator |
| 在算法里的位置 | "目标"那一侧(第 3 个参数) | "源"那一侧(前两个参数) |
这两个合起来就构成了完整的"流水线":
输入迭代器 ──► 算法 ──► 输出迭代器 (数据从哪来) (怎么处理) (数据到哪去) istream_iterator ostream_iterator vector.begin() back_inserter上一节讲过"数据从哪来、到哪去都是迭代器的事"——这一节补齐了"从哪来"。
二、istream_iterator:把输入流变成迭代器
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <iterator> // ★
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
// ── 用字符串流当"输入源"(方便测试;真实场景可以换成 cin 或文件流)──
istringstream data("5 2 8 1 9 3 7");
// ── ① 一句读完整个流 ────────────────────
istream_iterator<int> in(data); // ★ 绑到输入流
istream_iterator<int> eof; // ★ 默认构造 = "流的末尾"
vector<int> nums(in, eof); // ★ 用一对迭代器构造 vector
cout << "读进来 " << nums.size() << " 个数:";
for (int x : nums) cout << x << " ";
cout << endl;
// ── ② 对比传统写法(也是对的,只是更长)──
istringstream data2("5 2 8 1 9 3 7");
vector<int> nums2;
int x;
while (data2 >> x) nums2.push_back(x); // ★ 老写法
cout << "传统写法也读了 " << nums2.size() << " 个" << endl;
// ── ③ 输入迭代器能直接喂给算法 ──────────
istringstream data3("5 2 8 1 9 3 7");
istream_iterator<int> in3(data3), eof3;
cout << "直接累加(accumulate):"
<< accumulate(in3, eof3, 0) << " ← 数据没进过任何容器!" << endl;
// ── ④ 读字符串(按"空白"切分)──────────
istringstream words("the quick brown fox");
istream_iterator<string> wi(words), we;
vector<string> ws(wi, we);
cout << "读到 " << ws.size() << " 个词:";
for (const string &w : ws) cout << w << " ";
cout << endl;
// ── ⑤ 实用:一边读一边找(读一半就停)──
istringstream data4("10 20 30 40 50 60");
istream_iterator<int> in4(data4), eof4;
istream_iterator<int> found = find(in4, eof4, 40); // ★ 找到 40 就停
if (found != eof4)
cout << "找到了:" << *found << "(后面还有 "
<< distance(found, eof4) << " 个没读)" << endl;
return 0;
}
看到什么算成功:
读进来 7 个数:5 2 8 1 9 3 7
传统写法也读了 7 个
直接累加(accumulate):35 ← 数据没进过任何容器!
读到 4 个词:the quick brown fox
找到了:40(后面还有 2 个没读)
四个要点:
| 要点 | 说明 |
|---|---|
istream_iterator<int> eof; |
默认构造出来的就是"流的末尾" —— 它是所有输入流的"通用终点"(和容器的 end() 对应) |
vector<int> v(in, eof); |
一句读完(用区间构造函数)。它读到流结束或类型不匹配为止 |
| 能直接喂算法 | accumulate(in3, eof3, 0) —— 数据从没进过容器,这就是"流式处理" |
| 按"空白"切分 | >> 的规矩:遇空格/制表/换行都算一个词的结束(2.5.2 讲过的流的行为) |
③ 那句特别值得停一下:
accumulate直接从输入流里累加,一个容器都没用到。 如果数据量很大(比如几千万行日志),这就省掉了"先把全部读进内存"的开销 —— 这就是"流式算法"的价值。 ⑤ 那句也是:find找到就停,后面的数据根本不会被读(distance只是数了"还剩几个",并不真的读完)。"按需读取"在处理大文件时非常关键。
三、输入迭代器的"单遍"是什么意思
"单遍"(single-pass)是输入迭代器最容易被忽略、也最容易踩的限制:
#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
using namespace std;
int main() {
istringstream data("1 2 3 4 5");
// ❌ 看着像"能读好几遍",其实不行
istream_iterator<int> in(data), eof;
// 第一遍:读完了
int sum1 = 0;
for (istream_iterator<int> it = in; it != eof; ++it) sum1 += *it;
cout << "第一遍累加:" << sum1 << endl;
// 第二遍:in 已经走到末尾了,什么也读不到
int sum2 = 0;
for (istream_iterator<int> it = in; it != eof; ++it) sum2 += *it;
cout << "第二遍累加:" << sum2 << " ← 流已经被读光了!" << endl;
cout << "(这就是单遍:输入迭代器只能走一次,不能回头重读)" << endl;
return 0;
}
看到什么算成功:
第一遍累加:15
第二遍累加:0 ← 流已经被读光了!
(这就是"单遍":输入迭代器只能走一次,不能回头重读)
为什么"读过了就没了":输入流是"一次性的" —— 读过的那几个字符已经不在缓冲区里了(2.5 讲的"流"就是这个性质)。 所以"单遍"不是"输入迭代器故意小气",而是"它背后的东西本来就不能重读"。 同理,输出迭代器也是单遍(写出去的东西不能"取消")。 对比一下:
vector的迭代器是"多遍"的 —— 因为数据实实在在存在内存里,你想读几遍都行。"多遍"是"随机访问迭代器"的前置条件(13.4.3 / 13.4.5)。
四、动手 Demo:一个"边读边算"的小工具
任务:读一串成绩,不把全部数据装进容器,直接算出总分、最高分、平均分。
#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
istringstream data("88 95 72 88 60 95 88 100 55");
// ── ① 直接累加(不建容器)────────────
istream_iterator<int> in1(data), eof;
int total = accumulate(in1, eof, 0);
cout << "① 总分 = " << total << "(一个容器都没建)" << endl;
// ── ② 想知道"个数"和"最大",就得重新读一遍流 ──
// (因为输入迭代器是单遍的:上面已经读光了)
istringstream data2("88 95 72 88 60 95 88 100 55");
istream_iterator<int> in2(data2), eof2;
int n = 0, mx = -1, mn = 1000;
for (istream_iterator<int> it = in2; it != eof2; ++it) {
n++;
if (*it > mx) mx = *it;
if (*it < mn) mn = *it;
}
cout << "② 共 " << n << " 人,最高 " << mx << ",最低 " << mn << endl;
// ── ③ 折中:先装进容器,再想读几遍读几遍 ──
istringstream data3("88 95 72 88 60 95 88 100 55");
istream_iterator<int> in3(data3), eof3;
vector<int> scores(in3, eof3); // ★ 装一次
cout << "③ 装进 vector 后(" << scores.size() << " 个):" << endl;
cout << " 总分 = " << accumulate(scores.begin(), scores.end(), 0) << endl;
cout << " 最大 = " << *max_element(scores.begin(), scores.end()) << endl;
cout << " 平均 = " << (double)accumulate(scores.begin(), scores.end(), 0) / scores.size() << endl;
cout << " (想读几遍读几遍——这就是容器比流多的东西)" << endl;
return 0;
}
看到什么算成功:
① 总分 = 741(一个容器都没建)
② 共 9 人,最高 100,最低 55
③ 装进 vector 后(9 个):
总分 = 741
最大 = 100
平均 = 82.3333
("想读几遍读几遍"——这就是容器比流"多"的东西)
这段 Demo 把"流 vs 容器"的取舍讲明白了:
| 流(输入迭代器) | 容器(vector 等) |
|
|---|---|---|
| 内存 | 几乎不占(读一个处理一个) | 要装下全部数据 |
| 能读几遍 | 只有一遍 | 想读几遍读几遍 |
| 能随机访问 | ❌ | ✅(随机访问迭代器) |
| 能排序 | ❌(不能回头) | ✅ |
| 适用 | 大数据量、一次扫过就算完 | 需要反复访问/排序/查找 |
现实里最常用的组合是"两者结合":用
istream_iterator一句读进来,再用容器的各种能力去处理(Demo ③)。"全流式"只在数据大到装不进内存时才必须用。
30 秒实验
- 单遍验证:连续对同一个
istream_iterator做两次累加 → 第二次是 0。 - 读到类型不匹配就停:
istringstream data("1 2 abc 4");然后vector<int> v(in, eof);→ 只有 1、2 两个(遇到abc停止)。这是>>的行为。 find会"提前停":在istringstream里塞一万个数,find找第 3 个 → 瞬间返回(后面的没读)。- 忘了
<iterator>:注释掉 →'istream_iterator' was not declared。
常见误区
- "输入迭代器能写"——不能(
*it = x不合法)。要写用输出迭代器或普通迭代器。 - "输入迭代器能读好几遍"——只能一遍。读第二遍是空的。要反复访问,先把数据装进容器。
- "
istream_iterator会自己判断类型对不对"——它靠>>的规则:遇到不符合目标类型的内容就停止(1 2 abc 4只能读到 1、2)。它不会"跳过"错误继续读。 - "
eof要绑到某个流"——不用,默认构造的istream_iterator<T>就是"所有流的末尾"(它是个固定的"哨兵")。 - "读完再读就是又从头开始"——不是"从头开始",是"什么都没有"。流的位置只会往前走,不会回退(要回退得用
seekg,那是另一套 API)。 - "
istream_iterator读cin最方便"——方便但要小心:它默认会跳过所有空白(包括换行),所以"按行读"要用getline(那是另一个工具)。istream_iterator是"按"词"读",不是"按行读"。
自测三问(点开对答案)
1. **输入迭代器和输出迭代器分别是什么?它们在算法参数里各占哪一边?**——**输入迭代器只能读**(`x = *it`),**`istream_iterator` 是典型**;**输出迭代器只能写**(`*it = x`),**`ostream_iterator` / `back_inserter` 是典型**。**输入迭代器出现在算法的"源"侧(前两个参数),输出迭代器出现在"目标"侧(第三个参数)。** 两者都只能 `++`、都是单遍。 2. **"单遍"是什么意思?为什么输入迭代器只能单遍?**——**走一次就没了,不能回头重读**。**因为输入流本身就是一次性的**:读过的字符已经不在缓冲区了。**要读好几遍,必须先把数据装进容器**(容器的迭代器是"多遍"的)。 3. **`vector- ☐ 下一节(13.4.3 前向迭代器)