课程 · C++ 速查 · 读入与输出

读入与输出 例题与练习

先把「读对」练熟(getline 那个坑),再顺手把「打对」练一遍(行末那个空格)。「读快」这一半只有量大时才管。

2 道例题3 道练习5 份可运行代码回速查看这一组的 5 条 →
怎么用这一页

例题给全套:题面、样例、参考代码、以及一步步的讲解。先自己读题想一遍, 再看代码,最后对着讲解核一遍自己想的对不对。

练习只给题面、样例和一句提示,答案是折叠起来的 —— 请先自己写一版跑通了再展开。写不出来也别直接看答案,先回速查那一组找找。

每份代码都能直接点运行:输入框里已经填好了样例输入, 把它改一改再跑,是这一页最值钱的用法。

先把地基摆一遍

3 段

这一组里每一条都被本书量过,不是传说。而要先分清两件常被混在一起的事:「读对」和「读快」。 读错了是 WA(比如 getline 吃到一个空行),读慢了是 TLE —— 两者的症状完全不同,救法也完全不同。

★ 先说「读快」这一半:什么时候要管它,是一道算术题。数一数这道题总共要读多少个数:10⁶ 个以内,默认 cin 就够(约 0.16 秒);到 10⁷ 个,就必须关同步(第 6 章那道题读 47 MB,不关同步会超时)。⇒ 不到这个量级就别折腾,写快读反而容易写错。

★ 再说「读对」:>>自动跳过空格和换行,所以读一串数字从来不用管格式。但 getline 不跳 —— 它从当前位置一直读到换行为止。两者混用时,前一个 >> 留下的那个换行就会被 getline 当成「一个空行」读走。这是本组最常见的坑。

例题(2 道)

每道都给全套:题面 → 样例 → 参考代码 → 一步步的讲解。

例题 ① 先读个数,再读整行

ioEx1.cpp

第一行给一个整数 n,接下来 n 行,每行是一句可能带空格的话。请对每一行输出两个数:这一行有多少个字符、以及有多少个单词。

「单词」指的是被空格分开的、非空的一段。行首行尾都可能有空格,中间也可能有连续多个空格。

输入格式第一行一个整数 n1 ≤ n ≤ 100);接下来 n 行,每行一个字符串(可能含空格,长度 ≤ 1000)。
输出格式n 行,每行两个整数:该行的长度、该行的单词数。

输入

2
hello world
  a  b  c

输出

11 2
9 3
hello world 长 11、两个单词; a b c 长 9(行首两个空格也算字符)、三个单词。
先想一想

⚠ 这道题不能用 cin >> s —— 它遇到空格就停,读到的只是一个词。换成 getline 之后会撞上本组最有名的那个坑:先试着不加任何处理跑一遍,看第一行读到了什么。

ioEx1.cpp参考代码
// 例题 ①「先读个数,再读整行」—— 读入与输出组
//
// 本组最常见的坑就在这道题里:cin >> n 停在数字后面,
// **那一行的换行还留在输入里** ⇒ 紧接着的 getline 会立刻撞上它,读到一个空串。
// 改法是中间加一句 cin.ignore()(扔掉一个字符)。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
cin.ignore(); // ← 少了这一句,下面第一次 getline 读到的是空串
for (int t = 0; t < n; t++) {
string line;
getline(cin, line); // 一直读到换行,空格照收
int words = 0;
for (int i = 0; i < (int)line.size(); i++)
if (line[i] != ' ' && (i == 0 || line[i - 1] == ' ')) words++;
// 「一个单词的开头」= 自己不是空格,而前一个是空格(或者自己就在行首)
cout << line.size() << ' ' << words << "\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
这段代码是怎么想出来的
  1. cin >> n 读完那个数之后,停在数字的后面 —— 那一行的换行还留在输入里没人管。
  2. 紧接着的 getline(cin, line) 是「从当前位置一直读到换行」⇒ 它当场就撞上那个换行,于是读到一个空串,而且把那个换行吃掉了。⇒ 后面每一行都错位一格。
  3. 改法是中间加一句 cin.ignore(); —— 扔掉一个字符(就是那个换行)。(另一种写法是先白读一次 getline,效果一样。)
  4. 数单词不用别的库:一个字符是「单词的开头」,当且仅当它自己不是空格,而它前面是空格(或者它就在行首)。按这句话写一个 if,一遍扫完。
  5. ⚠ 而 >>getline 的这条差别,反过来也是 >> 的好处:>> 自动跳过空格和换行 ⇒ 全程只用 >> 时,输入的排版怎么折行都无所谓。
记住这一句

这一组最常见的 bug 就是这一个,而它的症状很有迷惑性:程序不崩、不报错,只是第一行读到了空的。

用到的写法:getline(cin, s) while (cin >> x)

例题 ② 读到文件结尾

ioEx2.cpp

输入是若干个整数,但题目不告诉你有几个。请输出它们的个数、总和,以及平均值(保留两位小数)。

⚠ 如果一个数都没有,个数和总和都输出 0,平均值输出 0.00

输入格式若干个整数(|aᵢ| ≤ 10⁹,个数 ≤ 10⁶),用空格或换行分隔,直到文件结尾。
输出格式第一行两个整数:个数和总和;第二行一个保留两位小数的实数。

输入

3 1 4 1 5

输出

5 14
2.80
5 个数,和 14,平均 2.8 ⇒ 打成两位小数是 2.80
先想一想

「不知道有几个」在 C++ 里有一个固定写法。⚠ 本地测试时要按 Ctrl+D(Linux)告诉程序输入结束,不然它会一直等。

ioEx2.cpp参考代码
// 例题 ②「读到文件结尾」—— 读入与输出组
//
// 题目不告诉你有几个数时用 while (cin >> x):
// 「cin >> x」这个表达式本身有一个值 —— 读成功了是真,读到头了是假。
// ⚠ 本地测试时要按 Ctrl+D(Linux)告诉它输入结束,不然它会一直等。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false); // 这两句放在 main 的第一行;放到读入之后就没用了
cin.tie(nullptr);
long long sum = 0;
int cnt = 0, x;
while (cin >> x) { sum += x; cnt++; }
cout << cnt << ' ' << sum << "\n";
// 保留两位小数:cout 要先说一句 fixed << setprecision(2),之后一直有效。
// ⚠ 上面关了同步,这里就**不要**再用 printf —— 两套输出混着用可能乱序。
cout << fixed << setprecision(2) << (cnt ? (double)sum / cnt : 0.0) << "\n";
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
这段代码是怎么想出来的
  1. while (cin >> x) —— cin >> x 这个表达式本身有一个值:读成功了是「真」,读到头(或者读到不是数字的东西)是「假」。⇒ 直接拿它当循环条件。
  2. 开头那两句 ios::sync_with_stdio(false); cin.tie(nullptr); 是关掉 cin 和 scanf 的同步,读入能快好几倍。⚠ 必须写在 main第一行 —— 写在读入之后就没用了。
  3. 什么时候需要它是一道算术题:数一数这道题总共要读多少个数。10⁶ 个以内默认 cin 就够(约 0.16 秒);到 10⁷ 个就必须关同步。这道题上限正好 10⁶ ⇒ 写上它,反正代价为零。
  4. 保留两位小数:cout << fixed << setprecision(2) << x,说一次之后一直有效。
  5. ⚠ 而既然关了同步,这里就不要再用 printf —— 两套输出混着用可能乱序。(要用 printf 的话,long long 的格式是 %lld,两位小数是 %.2f。)
记住这一句

第 22 章那道「导弹拦截」就是典型的「输入不给个数」。⇒ 而这一组真正要分清的是两件事:读错了是 WA,读慢了是 TLE,症状和救法完全不同。

用到的写法:while (cin >> x) ios::sync_with_stdio(false); cin.tie(nullptr); printf("%lld", x)

练习(3 道)

答案折叠着,先自己写一版。

练习 ① 行末不能留空格

ioTry1.cpp

n 个整数,请把它们倒序输出在一行里,两两之间恰好一个空格,行末不能有空格

输入格式第一行一个整数 n1 ≤ n ≤ 10⁵);第二行 n 个整数。
输出格式一行 n 个整数。

输入

4
1 2 3 4

输出

4 3 2 1
1 2 3 4 倒过来是 4 3 2 1 —— 最后那个 1 后面什么都没有
提示

最顺手的写法是 cout << a[i] << ' ';,而它正好会在行末多留一个空格。两条改法:要么「我不是第一个就先打个空格」,要么「我不是最后一个才打空格」。

ioTry1.cpp参考答案
记住这一句

这不是小题大做:第 47 章那道题上,「行末多一个空格」在 300 轮对拍里被抓了 284 次,而把每行末尾的空格去掉再比一次 ⇒ 整列变成 0。⇒ 肉眼看两份输出「明明一模一样」,所以对拍从来都是逐字节比。

用到的写法:'\n' 而不是 endl

练习 ② 每行先告诉你有几个数

ioTry2.cpp

第一行一个整数 n,接下来 n 行,每行先是一个整数 k,然后是 k 个整数。请对每一行输出这 k 个数的和。

输入格式第一行 n1 ≤ n ≤ 1000);接下来 n 行,每行一个 k1 ≤ k ≤ 1000)和 k 个整数(|aᵢ| ≤ 10⁹)。
输出格式n 行,每行一个整数。

输入

2
3 1 2 3
2 10 20

输出

6
30
第一行 3 个数和是 6,第二行 2 个数和是 30。
提示

这道题看着要「按行读」,其实完全不用管行。想想 >> 会不会跳过换行。

ioTry2.cpp参考答案
记住这一句

⇒ 一旦全程只用 >>,输入的排版就不是问题;只有 getline 才在乎行。

用到的写法:while (cin >> x)

练习 ③ 把整个输入倒着打出来

ioTry3.cpp

把输入的每一行原样保存下来,然后按相反的顺序逐行输出。

⚠ 行里可能有空格,行数事先不知道。

输入格式若干行文本,直到文件结尾(行数 ≤ 10⁵,每行长度 ≤ 1000)。
输出格式把输入的各行倒序输出,每行一行。

输入

first line
second
third

输出

third
second
first line
三行倒过来就是 thirdsecondfirst line —— 注意最后那行里有空格。
提示

两件事各对应一个写法:「行里有空格」决定了用哪个读;「行数不知道」决定了循环条件怎么写。这两个写法能拼在一起。

ioTry3.cpp参考答案

用到的写法:getline(cin, s) while (cin >> x)