C++复健笔记 Vol.6:保研夏令营机考 IO 备考指南

摘要

面向保研夏令营机考的 C++ IO 复习:不用万能头,少做非必要优化,重点避开 getline、EOF、格式化输出和本地调试坑。

这一篇重新整理一下 C++ 的 IO。目标不是把标准库流讲完整,而是面向保研夏令营机考:能稳、能默写、少踩坑。

也就是说,我不再把 #include <bits/stdc++.h> 和快 IO 当成默认模板。机考环境可能是 Dev-C++ 或比较老的编译器,万能头不一定稳;大部分夏令营题的数据量也没有大到必须先优化 IO。

头文件:不用万能头

考场上最稳妥的写法是只引入需要的标准头文件。

需要使用的功能头文件
cin / cout#include <iostream>
string 字符串#include <string>
stringstream 字符串流解析#include <sstream>
vector 动态数组#include <vector>
sort / reverse 等算法#include <algorithm>
setprecision 保留小数#include <iomanip>
toupper / isdigit 字符处理#include <cctype>
freopen / printf#include <cstdio>

考场常用模板可以先背这一版:

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;

int main() {
    // code here

    return 0;
}

如果题目需要字符判断,再加 #include <cctype>;如果本地调试要用 freopen,再加 #include <cstdio>。

OJ 输入和 EOF

OJ 判题时,系统会把测试数据重定向到程序的标准输入里。对代码来说,就是从 cin 读数据。

文件一定有结尾,所以这种写法会在读到 EOF 时自动停下:

int x;
while (cin >> x) {
    cout << x << '\n';
}

如果是一行一行读,也一样:

string line;
while (getline(cin, line)) {
    cout << line << '\n';
}

本地在 Dev-C++ 黑框里手动测试时,如果想模拟 EOF,Windows 下一般是输入完数据后按 Ctrl + Z,再按回车。

注意,不要写这种循环:

while (!cin.eof()) {
    cin >> x;
    // ...
}

更稳的写法是把“读入动作”直接放进循环条件:

while (cin >> x) {
    // ...
}

不定长数据:getline + stringstream

如果题目说每一行有不定数量的整数,最舒服的组合是 getline + stringstream。

比如:读入若干行,每行有若干整数,输出每行的和。

#include <iostream>
#include <string>
#include <sstream>
using namespace std;

int main() {
    string line;

    while (getline(cin, line)) {
        if (line.empty()) {
            continue;
        }

        stringstream ss(line);

        int sum = 0;
        int x;
        while (ss >> x) {
            sum += x;
        }

        cout << sum << '\n';
    }

    return 0;
}

这个模板很适合处理:

  • 每行数字个数不固定;
  • 输入行数没有提前给出;
  • 每一行都要单独统计或分析。

cin 和 getline 混用

cin >> x 读走数据,但通常不会读走行末的换行符。紧接着写 getline(cin, line),就可能直接读到一个空字符串。

典型场景:先读一个整数 T,后面跟着 T 行字符串,而且字符串里可能有空格。

int T;
cin >> T;
cin.ignore();

string line;
for (int i = 0; i < T; i++) {
    getline(cin, line);
    // 处理 line
}

这里的 cin.ignore() 不能忘,它负责吃掉 T 后面的那个回车。

如果担心这一行里还有别的残留内容,可以写更完整的版本:

#include <limits>

cin.ignore(numeric_limits<streamsize>::max(), '\n');

不过考场上,多数题只是 cin >> T 后接整行字符串,cin.ignore() 通常够用。

字符串拆分

如果一行数据按空格分隔,用 stringstream >> 就够了:

string line = "Tom 90 85 92";
stringstream ss(line);

string name;
int a, b, c;
ss >> name >> a >> b >> c;

如果按逗号这种单字符分隔,可以用 getline 的第三个参数:

string line = "Tom,90,85,92";
stringstream ss(line);

string part;
while (getline(ss, part, ',')) {
    cout << part << '\n';
}

如果有多种分隔符,比如空格、逗号、句号混在一起,可以用 find_first_of。

#include <iostream>
#include <string>
#include <vector>
using namespace std;

vector<string> splitMulti(const string& s, const string& delimiters) {
    vector<string> res;
    size_t start = 0;

    while (start < s.size()) {
        size_t end = s.find_first_of(delimiters, start);
        string word = s.substr(start, end - start);

        if (!word.empty()) {
            res.push_back(word);
        }

        if (end == string::npos) {
            break;
        }
        start = end + 1;
    }

    return res;
}

用法:

vector<string> words = splitMulti("hello, world. C++", " ,.");

这种写法不算短,但很稳定。考场上如果分隔符复杂,不要硬用一堆 if 手搓。

格式化输出

保留小数用 <iomanip>:

#include <iomanip>

double x = 3.1415926;
cout << fixed << setprecision(2) << x << '\n'; // 3.14

注意,fixed 和 setprecision(2) 设置后,会影响后续的浮点数输出。

cout << fixed << setprecision(2);
cout << 3.1415926 << '\n'; // 3.14
cout << 2.0 << '\n';       // 2.00

如果考场上实在忘了 <iomanip>,也可以用 C 风格输出:

#include <cstdio>

printf("%.2f\n", x);

但不要一会儿用 cout 的格式控制,一会儿又切到 printf。能统一就统一。

vector 输出不要多空格

很多 OJ 对行尾空格不严格,但不要把希望寄托在“它应该能过”上。输出数组时,最稳的是用下标控制空格。

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

for (int i = 0; i < (int)v.size(); i++) {
    if (i > 0) {
        cout << ' ';
    }
    cout << v[i];
}
cout << '\n';

核心就是:从第二个元素开始,先输出空格,再输出元素。

文件 IO 和本地调试

OJ 提交时,正常情况下不需要写文件 IO,也不需要写 freopen。直接用 cin 和 cout,判题系统会自动处理输入输出。

本地调试时,可以在 .cpp 同目录下放一个 1.in 文件,然后临时加:

#include <cstdio>

freopen("1.in", "r", stdin);

完整位置一般放在 main 一开始:

int main() {
    freopen("1.in", "r", stdin);

    // code here

    return 0;
}

提交前一定要删掉或注释掉这一行:

// freopen("1.in", "r", stdin);

否则 OJ 上没有这个本地文件,轻则读不到数据,重则直接报错。

终极 Clean 模板

如果题目是“先给 T,再给 T 行可能包含空格的内容”,可以套这个:

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;

int main() {
    // 本地调试时再打开,提交前删掉或注释掉
    // freopen("1.in", "r", stdin);

    int T;
    cin >> T;
    cin.ignore();

    while (T--) {
        string line;
        getline(cin, line);

        // 处理 line
    }

    return 0;
}

如果打开了 freopen 那行,记得补 #include <cstdio>。

如果题目没有给行数,直接读到 EOF:

string line;
while (getline(cin, line)) {
    // 处理 line
}

考前一分钟自查

  • 有没有删掉 #include <bits/stdc++.h>?
  • 有没有删掉或注释掉本地调试用的 freopen?
  • 先读数字再读整行时,有没有写 cin.ignore()?
  • 需要读入含空格的字符串时,有没有用 getline?
  • 输出浮点数时,有没有加 #include <iomanip>?
  • 输出 vector 时,末尾有没有多余空格?
  • 读到 EOF 时,是不是写成了 while (cin >> x) 或 while (getline(cin, line))?
  • 题目要求按行处理时,有没有避免直接用 cin >> s 把空格后的内容丢掉?

小结

这篇的重点不是“C++ IO 有多少种玩法”,而是机考时怎么少丢分:

  • 标准头文件比万能头更稳。
  • 简单 cin / cout 已经够应付大多数夏令营机考题。
  • getline 和 cin >> 混用时,先处理掉残留换行。
  • 不定长行数据优先想 getline + stringstream。
  • 输出格式、尾随空格、本地 freopen 都是小坑,但每一个都可能让人白丢分。