纯 C++ 流式数据输入处理
1、单组简单数据
1.1 单个整数、浮点数、字符、字符串
说明: cin >> 自动跳过前导空白符(空格、制表符、换行),读取到下一个空白符为止。
cpp
#include<iostream>
#include<string>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n; // 整数
double d; // 浮点数
char ch; // 单个字符
std::string s; // 字符串(不含空格)
std::cin >> n >> d >> ch >> s;
// 输出验证读取结果
std::cout << "int: " << n << '\n';
std::cout << "double: " << d << '\n';
std::cout << "char: " << ch << '\n';
std::cout << "string: " << s << '\n';
return 0;
}
plaintext
42 3.14 X hello_world
1.2 一行内多个同类型数据(已知个数)
说明: 先读个数 N,再用循环依次读入 N 个同类型数据。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n; // 先读入数据个数
std::vector<int> a(n);
for (int i = 0; i < n; i++)
{
std::cin >> a[i]; // 依次读入每个整数
}
// 输出验证
std::cout << "读取了 " << n << " 个整数:";
for (int i = 0; i < n; ++i)
{
std::cout << ' ' << a[i];
}
std::cout << '\n';
return 0;
}
plaintext
5
10 20 30 40 50
1.3 一行内不定个数的整数,直到行尾
说明: 先用 getline 读入整行,再用 istringstream 逐个解析整数。
cpp
#include<iostream>
#include<sstream>
#include<string>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string line;
std::getline(std::cin, line); // 读入整行
std::istringstream iss(line); // 将整行放入字符串流
std::vector<int> nums;
int x;
while (iss >> x) // 逐个解析整数,直到流结束
{
nums.push_back(x);
}
// 输出验证
std::cout << "本行共读取 " << nums.size() << " 个整数:";
for (std::size_t i = 0; i < nums.size(); ++i)
{
std::cout << ' ' << nums[i];
}
std::cout << '\n';
return 0;
}
plaintext
7 14 21 28 35 42 49 56 63
2. 单组多行数据
2.1 给定行数 N,接下来 N 行每行一个数据
说明: 先读 N,再循环 N 次,每次读入一个数据。cin >> 会自动跳过换行。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n; // 读入行数
std::vector<int> data(n);
for (int i = 0; i < n; i++)
{
std::cin >> data[i];
}
// 输出验证
std::cout << "读取了 " << n << " 行数据:\n";
for (int i = 0; i < n; i++)
{
std::cout << " [" << i << "] = " << data[i] << '\n';
}
return 0;
}
plaintext
4
100
200
300
400
2.2 不定行数,直到 EOF(整数)
说明: 利用 cin >> 的返回值判断是否成功读取,读到 EOF 时自动停止。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::vector<int> nums;
int x;
// cin >> x 在读到 EOF 或类型不匹配时返回 false,循环自动终止
while (std::cin >> x)
{
nums.push_back(x);
}
// 输出验证
std::cout << "共读取 " << nums.size() << " 个整数:";
for (std::size_t i = 0; i < nums.size(); i++)
{
std::cout << ' ' << nums[i];
}
std::cout << '\n';
return 0;
}
plaintext
11
22
33
44
55
(Windows) Ctrl+Z 然后回车输入 EOF 信号
2.3 不定行数,直到 EOF(整行字符串)
说明: 用 getline 逐行读取,空行也会被读入(内容为空字符串)。
cpp
#include<iostream>
#include<string>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::vector<std::string> lines;
std::string line;
// getline 在遇到 EOF 时返回false,循环自动终止
while (std::getline(std::cin, line))
{
lines.push_back(line);
}
// 输出验证(含空行)
std::cout << "共读取 " << lines.size() << " 行:\n";
for (std::size_t i = 0; i < lines.size(); ++i)
{
std::cout << " line[" << i << "] = \"" << lines[i] << "\"\n";
}
return 0;
}
plaintext
Hello World
This is a test
Goodbye
(Windows) Ctrl+Z 然后回车输入 EOF 信号
3. 多组测试用例
3.1 第一行给出 T,然后循环 T 次
说明: 最标准的多组测试用例格式,先读 T,再用 for 循环处理每组数据。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T;
std::cin >> T; // 读入测试用例总数
for (int t = 1; t <= T; t++)
{
int n;
std::cin >> n; // 每组用例先读元素个数
std::vector<int> a(n);
for (int i = 0; i < n; i++)
{
std::cin >> a[i];
}
std::cout << "Case #" << t << ": n=" << n << ", data:";
for (int i = 0; i < n; i++)
{
std::cout << ' ' << a[i];
}
std::cout << '\n';
}
return 0;
}
plaintext
3
2
10 20
3
100 200 300
1
42
3.2 未给出 T,读取到 EOF 结束
说明: 用 while(cin >> n) 判断是否还有新用例,读到 EOF 自动退出。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
int caseNo = 0;
// 每次循环读取一个新用例的 N,读到 EOF 时终止
while (std::cin >> n)
{
caseNo++;
std::vector<int> a(n);
for (int i = 0; i < n; i++)
{
std::cin >> a[i];
}
// 输出验证
std::cout << "Case #" << caseNo << ": n=" << n << ", data:";
for (int i = 0; i < n; i++)
{
std::cout << ' ' << a[i];
}
std::cout << '\n';
}
return 0;
}
plaintext
2
10 20
3
100 200 300
1
42
3.3 以特殊标记结束(如 0 0)
说明: 每次读取后检查是否为终止标记,若是则 break 退出循环。
cpp
#include <iostream>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int a, b;
int caseNo = 0;
while (std::cin >> a >> b)
{
if (a == 0 && b == 0) break;
caseNo++;
std::cout << "Case " << caseNo << ": a=" << a << ", b=" << b << '\n';
}
return 0;
}
plaintext
3 5
10 20
7 8
0 0
3.4 以单词 "stop" 结束
说明: 逐个读入字符串,遇到特定终止单词时退出循环。
cpp
#include<iostream>
#include<string>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string word;
int count = 0;
while (std::cin >> word)
{
if (word == "stop") break;
count++;
std::cout << "[" << count << "] " << word << '\n';
}
std::cout << "共读取 " << count << " 个单词\n";
return 0;
}
plaintext
apple banana cherry date stop
3.5 嵌套结束:每组用例内含以 0 结束的子序列
说明: 外层以 n=0 终止用例,内层每组子序列以 0 终止。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
int caseNo = 0;
// 外层:读取 n,n=0 时终止所有用例
while (std::cin >> n && n != 0)
{
caseNo++;
std::vector<std::vector<int>> groups;
for (int i = 0; i < n; i++)
{
std::vector<int> seq;
int val;
// 内层:读取子序列,遇到 0 终止当前子序列
while (std::cin >> val && val != 0)
{
seq.push_back(val);
}
groups.push_back(seq);
}
// 输出验证
std::cout << "Case " << caseNo << " (n=" << n << "):\n";
for (int i = 0; i < n; i++)
{
std::cout << " group[" << i << "]:";
for (std::size_t j = 0; j < groups[i].size(); j++)
{
std::cout << ' ' << groups[i][j];
}
std::cout << '\n';
}
}
return 0;
}
plaintext
2
1 2 3 0
4 5 0
3
10 0
20 30 40 0
50 60 0
0
4. 二维数组 / 矩阵
4.1 给定 N 行 M 列,读 N×M 个元素
说明: 标准的二维矩阵读入方式,用二重循环逐个读入。cin >> 自动跳过换行。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m; // 读入行数和列数
// 创建 N×M 的二维矩阵
std::vector<std::vector<int>> mat(n, std::vector<int>(m));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
std::cin >> mat[i][j]; // 逐个读入矩阵元素
}
}
// 输出验证
std::cout << "矩阵 " << n << "x" << m << ":\n";
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (j > 0) std::cout << '\t';
std::cout << mat[i][j];
}
std::cout << '\n';
}
return 0;
}
plaintext
3 4
1 2 3 4
5 6 7 8
9 10 11 12
4.2 仅给定行 N,每行列数不定(锯齿形矩阵)
说明: 先读 N,再逐行 getline,用 istringstream 解析每行的不定个数整数。注意 cin >> n 后需 ignore 掉残留换行符。
cpp
#include<iostream>
#include<sstream>
#include<string>
#include<vector>
#include<limits>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
// 清除 cin >> n 后残留的换行符,否则 getline 会读到空行
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::vector<std::vector<int>> jagged(n);
for (int i = 0; i < n; i++)
{
std::string line;
std::getline(std::cin, line); // 读入整行
std::istringstream iss(line); // 解析行内整数
int val;
while (iss >> val)
{
jagged[i].push_back(val);
}
}
// 输出验证
std::cout << "锯齿形矩阵 (" << n << " 行):\n";
for (int i = 0; i < n; i++)
{
std::cout << " row[" << i << "] (" << jagged[i].size() << " 个):";
for (std::size_t j = 0; j < jagged[i].size(); j++)
{
std::cout << ' ' << jagged[i][j];
}
std::cout << '\n';
}
return 0;
}
plaintext
4
1 2 3
4 5
6 7 8 9 10
11
4.3 字符矩阵(逐字符读入,跳过空白)
说明: 用 cin >> char 逐个读入字符,自动跳过空格和换行。适用于字符之间有空格分隔的矩阵。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<char>> grid(n, std::vector<char>(m));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
std::cin >> grid[i][j]; // 读入单个字符,自动跳过空白
}
}
// 输出验证
std::cout << "字符矩阵 " << n << "×" << m << ":\n";
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
std::cout << grid[i][j];
}
std::cout << '\n';
}
return 0;
}
plaintext
3 5
. # . # .
# . # . #
. . # . .
4.4 字符矩阵(逐行字符串读入,保留所有字符)
说明: 用 getline 逐行读入,保留行内所有字符(包括空格)。适用于字符之间无空格、需要保留空白字符的场景。
cpp
#include<iostream>
#include<string>
#include<vector>
#include<limits>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
// 清除 cin >> n >> m 后的残留换行
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::vector<std::string> grid(n);
for (int i = 0; i < n; i++)
{
std::getline(std::cin, grid[i]); // 读入整行字符串
}
// 输出验证
std::cout << "字符矩阵 " << n << "×" << m << ":\n";
for (int i = 0; i < n; i++)
{
std::cout << " row[" << i << "] = \"" << grid[i] << "\" (len=" << grid[i].size() << ")\n";
}
return 0;
}
plaintext
3 5
.#.#.
#.#.#
..#..
5. 字符串输入
5.1 读取单词(不含空格)
说明: cin >> string 自动跳过前导空白,读到下一个空白符为止。
cpp
#include <iostream>
#include <string>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string word;
std::cin >> word; // 只读一个单词,前导空白被自动跳过
std::cout << "word = \"" << word << "\"\n";
return 0;
}
plaintext
HelloWorld
5.2 读取整行(含空格),注意残留换行符
说明: cin >> 后必须用 ignore 清除残留换行符,否则 getline 会读到空行。
cpp
#include<iostream>
#include<string>
#include<limits>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int id;
std::cin >> id;
// 关键:清除 cin >> id 后的残留换行符
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::string line;
std::getline(std::cin, line); // 现在能正常读入整行
// 输出验证
std::cout << "id = " << id << '\n';
std::cout << "line = \"" << line << "\"\n";
return 0;
}
plaintext
42
Hello World, this is a test!
5.3 连续读取多行直到 EOF(含空行)
说明: 用 while(getline) 循环,空行会被读入为空字符串 "",不会跳过。
cpp
#include<iostream>
#include<string>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::vector<std::string> lines;
std::string line;
while (std::getline(std::cin, line))
{
lines.push_back(line); // 空行也会被存入
}
// 输出验证(空行显示为空字符串)
for (std::size_t i = 0; i < lines.size(); i++)
{
std::cout << "[" << i << "] \"" << lines[i] << "\"\n";
}
return 0;
}
plaintext
First line
Third line after empty line
Two empty lines above
(Windows) Ctrl+Z 然后回车输入 EOF 信号
5.4 读取字符数组(不推荐,但兼容场景)
说明: cin.getline(buf, size) 是 C++ istream 成员函数(非 C 风格),读入最多 size-1 个字符到 char 数组中,以 '\0' 结尾。推荐使用 std::string + std::getline 替代。
cpp
#include<iostream>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
char buf[256];
std::cin.getline(buf, 256); // 读入整行到字符数组
std::cout << "buf = \"" << buf << "\"\n";
return 0;
}
plaintext
This is a char array test with spaces.
6. 复合结构
6.1 一行包含混合类型数据
说明: cin >> 可链式读取不同类型的变量,自动按类型解析。
cpp
#include<iostream>
#include<string>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int id;
double score;
std::string name;
// 一行内一次读入 int double string
std::cin >> id >> score >> name;
// 输出验证
std::cout << "id=" << id << ", score=" << score << ", name=" << name << '\n';
return 0;
}
plaintext
1001 95.5 Alice
6.2 N 行每行 string int double 组合
说明: 先读 N,再循环 N 次,每次读入一组混合类型数据。
cpp
#include<iostream>
#include<string>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::string> names(n);
std::vector<int> ages(n);
std::vector<double> scores(n);
for (int i = 0; i < n; i++)
{
// 每行一次读入姓名、年龄、分数
std::cin >> names[i] >> ages[i] >> scores[i];
}
// 输出验证
for (int i = 0; i < n; i++)
{
std::cout << "[" << i << "] " << names[i] << ", age=" << ages[i] << ", score=" << scores[i] << '\n';
}
return 0;
}
plaintext
3
Alice 20 92.5
Bob 22 88.0
Charlie 21 95.3
6.3 读入 N 对数据(使用 std::pair)
说明: 先读 N,再循环 N 次,每次读入一组混合类型数据。
cpp
#include<iostream>
#include<vector>
#include<utility>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::pair<int, int>> data(n);
for (int i = 0; i < n; i++)
{
std::cin >> data[i].first >> data[i].second; // 读入一对数据
}
// 输出验证
std::cout << n << " 对数据:\n";
for (int i = 0; i < n; i++)
{
std::cout << " pair[" << i << "] = (" << data[i].first << ", " << data[i].second << ")\n";
}
return 0;
}
plaintext
4
1 10
2 20
3 30
4 40
7. 树与图的输入
7.1 无向树(N 个节点,N-1 条边,1-based)
说明: 读入 N-1 条边,每条边连接两个节点,构建无向邻接表。节点编号从 1 开始,因此邻接表大小为 N+1。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
// 节点编号 1~n,邻接表大小为 n+1
std::vector<std::vector<int>> adj(n + 1);
for (int i = 0; i < n - 1; i++)
{
int u, v;
std::cin >> u >> v;
adj[u].push_back(v); // 无向边,双向存储
adj[v].push_back(u);
}
// 输出验证
std::cout << "无向树邻接表 (n=" << n << "):\n";
for (int i = 1; i <= n; ++i)
{
std::cout << " node " << i << ":";
for (std::size_t j = 0; j < adj[i].size(); ++j)
{
std::cout << ' ' << adj[i][j];
}
std::cout << '\n';
}
return 0;
}
plaintext
6
1 2
1 3
2 4
2 5
3 6
7.2 有向带权图(N 个节点,M 条边)
说明: 读入 M 条有向边,每条边包含起点、终点、权重,构建邻接表。
cpp
#include<iostream>
#include<vector>
#include<utility>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
// 邻接表矩阵:每个节点对应一组(终点,权重)
std::vector<std::vector<std::pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; i++)
{
int u, v, w;
std::cin >> u >> v >> w;
adj[u].push_back({ v, w }); // 有向边,只存单向
}
// 输出验证
std::cout << "有向带权图邻接表 (n=" << n << ", m=" << m << "):\n";
for (int i = 1; i <= n; ++i)
{
std::cout << " node " << i << ":";
for (std::size_t j = 0; j < adj[i].size(); ++j)
{
std::cout << " ->(" << adj[i][j].first
<< ", w=" << adj[i][j].second << ")";
}
std::cout << '\n';
}
return 0;
}
plaintext
5 6
1 2 10
1 3 5
2 4 3
3 2 2
3 5 8
5 4 1
8. 其他典型输入
8.1 第一行 N 和 K,然后一行 N 个整数
说明: 最常见的竞赛输入格式之一,先读两个参数,再读数组。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k; // 读入数组大小和参数K
std::vector<int> a(n);
for (int i = 0; i < n; i++)
{
std::cin >> a[i];
}
// 输出验证
std::cout << "n=" << n << ", k=" << k << '\n';
std::cout << "数组:";
for (int i = 0; i < n; ++i)
{
std::cout << ' ' << a[i];
}
std::cout << '\n';
return 0;
}
plaintext
7 3
1 5 2 8 3 7 4
8.2 包含前导零的数据(电话号码 / 身份证号等,必须用 string)
说明: 前导零在整数类型中会被丢弃,因此必须用 string 存储。
cpp
#include<iostream>
#include<string>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::string> ids(n);
for (int i = 0; i < n; i++)
{
std::cin >> ids[i]; // 用 string 保留前导零
}
// 输出验证
std::cout << "读取了 " << n << " 个编号:\n";
for (int i = 0; i < n; ++i)
{
std::cout << " [" << i << "] = " << ids[i] << '\n';
}
return 0;
}
plaintext
3
001234
00000001
110101199001011234
8.3 逗号分隔的数据
说明: 先用 getline 读入整行,再用 getline(iss, token, ',') 按逗号分割。
cpp
#include<iostream>
#include<sstream>
#include<string>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string line;
std::getline(std::cin, line); // 读入整行
std::istringstream iss(line);
std::vector<std::string> tokens;
std::string token;
while (std::getline(iss, token, ','))
{
tokens.push_back(token);
}
// 尝试将每个 token 解析为整数
std::vector<int> nums;
for (std::size_t i = 0; i < tokens.size(); i++)
{
std::istringstream tokenStream(tokens[i]);
int val;
if (tokenStream >> val)
{
nums.push_back(val);
}
}
// 输出验证
std::cout << "字符串 tokens:";
for (std::size_t i = 0; i < tokens.size(); ++i)
{
std::cout << " \"" << tokens[i] << "\"";
}
std::cout << '\n';
std::cout << "解析后的整数:";
for (std::size_t i = 0; i < nums.size(); ++i)
{
std::cout << ' ' << nums[i];
}
std::cout << '\n';
return 0;
}
plaintext
10,20,30,40,50
8.4 极大数据量(快速 I/O 演示)
说明: ios::sync_with_stdio(false) 和 cin.tie(nullptr) 是必须的两行加速代码,使 cin 速度与 scanf 相当。
cpp
#include<iostream>
#include<vector>
int main()
{
// 关键:关闭 C/C++ 标准流同步,大幅提升 I/O 速度
std::ios::sync_with_stdio(false);
// 解除 cin 与 cout 的绑定,避免每次 cin 前自动 flush cout
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++i)
{
std::cin >> a[i];
}
// 输出验证(仅展示前 5 个)
std::cout << "读取了 " << n << " 个整数\n";
for (int i = 0; i < n && i < 5; ++i)
{
std::cout << " a[" << i << "] = " << a[i] << '\n';
}
if (n > 5)
{
std::cout << " ... (省略 " << (n - 5) << " 个)\n";
}
return 0;
}
plaintext
10
999888777 111222333 444555666 777888999 123456789 987654321 111111111 222222222 333333333 444444444
9. 健壮性与常见陷阱演示
9.1 cin >> 与 getline 混用的换行符残留问题
说明: cin >> 读完数字后,换行符 '\n' 仍留在流中,若不 ignore,getline 会立即读到空字符串。
cpp
#include<iostream>
#include<string>
#include<limits>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
// 关键:清除 cin >> n 后残留的换行符
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::string name;
std::getline(std::cin, name); // 现在能正确读入带空格的姓名
// 输出验证
std::cout << "n = " << n << '\n';
std::cout << "name = \"" << name << "\"\n";
return 0;
}
plaintext
5
Zhang San
9.2 流状态清除(输入类型错误时的恢复)
说明: 当 cin >> int 遇到非数字时,流进入 fail 状态。必须先 clear() 恢复流状态,再 ignore() 跳过错误输入。
cpp
#include<iostream>
#include<string>
#include<limits>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::vector<int> nums;
int x;
int maxCount = 5; // 最多读取 5 个有效整数
while (maxCount > 0)
{
if (std::cin >> x)
{
nums.push_back(x); // 读取成功
maxCount--;
}
else
{
// 读取失败:先清除错误状态,再跳过当前行的剩余内容
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
// 输出验证
std::cout << "成功读取的整数:";
for (std::size_t i = 0; i < nums.size(); ++i) {
std::cout << ' ' << nums[i];
}
std::cout << '\n';
return 0;
}
plaintext
10
abc
20
xyz
30
40
50
9.3 EOF 判断与流状态的综合说明
说明: while(cin >> x) 是最安全的读法,同时处理 EOF 和类型错误。cin.eof() 可用于区分"正常结束"和"输入错误"。
cpp
#include<iostream>
#include<string>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int x;
while (std::cin >> x)
{
std::cout << "读取整数: " << x << '\n';
}
// 判断循环终止原因
if (std::cin.eof())
{
std::cout << "已到达文件末尾 (EOF)\n";
}
else
{
std::cout << "输入类型错误或流异常\n";
}
return 0;
}
plaintext
100
200
300
10. 综合实战:多组用例 + 混合输入类型
说明: 演示在单个程序中混合使用 cin >>、getline、istringstream、ignore,处理包含标题、整数数组、浮点数行、图边等多种输入的复杂场景。
cpp
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <utility>
#include <limits>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T;
std::cin >> T;
for (int t = 1; t <= T; ++t)
{
// 清除残留换行
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// 1. 读取用例标题(含空格,必须用 getline)
std::string title;
std::getline(std::cin, title);
// 2. 读取 n 和 k
int n, k;
std::cin >> n >> k;
// 3. 读取 n 个整数的数组
std::vector<int> a(n);
for (int i = 0; i < n; ++i)
{
std::cin >> a[i];
}
// 4. 清除数组行末尾换行,再读一行浮点数
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::string line;
std::getline(std::cin, line);
std::istringstream iss(line);
std::vector<double> vals;
double d;
while (iss >> d)
{
vals.push_back(d);
}
// 5. 读取图边数据
int m;
std::cin >> m;
std::vector<std::pair<std::pair<int, int>, int>> edges(m);
for (int i = 0; i < m; ++i)
{
std::cin >> edges[i].first.first
>> edges[i].first.second
>> edges[i].second;
}
// 输出验证
std::cout << "=== Case " << t << " ===\n";
std::cout << "Title: " << title << '\n';
std::cout << "n=" << n << ", k=" << k << '\n';
std::cout << "Array:";
for (int i = 0; i < n; ++i) std::cout << ' ' << a[i];
std::cout << '\n';
std::cout << "Doubles:";
for (std::size_t i = 0; i < vals.size(); ++i) std::cout << ' ' << vals[i];
std::cout << '\n';
std::cout << "Edges (m=" << m << "):\n";
for (int i = 0; i < m; ++i)
{
std::cout << " " << edges[i].first.first << " -> "
<< edges[i].first.second
<< " (w=" << edges[i].second << ")\n";
}
}
return 0;
}
plaintext
2
Graph Problem Alpha
3 2
10 20 30
1.5 2.5 3.5 4.5
2
1 2 10
2 3 20
String Query Beta
4 1
5 6 7 8
9.9 8.8
1
1 4 99
11. 罕见输入格式
11.1 处理括号/特殊字符包裹的坐标数据(如 (x,y))
说明: 先用 cin >> 读入整个 token,将非数字/负号字符替换为空格,再用 istringstream 解析出两个整数坐标。
cpp
#include<iostream>
#include<sstream>
#include<string>
#include<vector>
#include<utility>
#include<cctype>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::pair<int, int>> coords(n);
for (int i = 0; i < n; i++)
{
std::string token;
std::cin >> token; // 读入如 "(10,20)" 的整体
// 将非数字、非负号的字符替换为空格
for (std::size_t j = 0; j < token.size(); j++)
{
char c = token[j];
if (!std::isdigit(c) && c != '-')
{
token[j] = ' ';
}
}
// 从清理后的字符串中解析两个整数
std::istringstream iss(token);
iss >> coords[i].first >> coords[i].second;
}
// 输出验证
std::cout << "解析后的坐标:\n";
for (int i = 0; i < n; ++i)
{
std::cout << " [" << i << "] = ("
<< coords[i].first << ", "
<< coords[i].second << ")\n";
}
return 0;
}
plaintext
3
(10,20)
(-5,100)
(42,-99)
11.2 自定义分隔符读取(如分号 ; 或竖线 | 分隔)
说明: getline(iss, token, delim) 的第三个参数可指定任意分隔符。
cpp
#include<iostream>
#include<sstream>
#include<string>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string line;
std::getline(std::cin, line); // 读入整行
std::istringstream iss(line);
std::vector<std::string> tokens;
std::string token;
// 以 ';' 为分隔符逐个提取 token
while (std::getline(iss, token, ';'))
{
tokens.push_back(token);
}
// 输出验证
std::cout << "按分号分割后的 tokens:\n";
for (std::size_t i = 0; i < tokens.size(); ++i)
{
std::cout << " [" << i << "] = \"" << tokens[i] << "\"\n";
}
return 0;
}
plaintext
apple;banana;cherry;date;elderberry
11.3 跨行/带引号的长字符串 Token(含内部换行)
说明: 用 cin.get(ch) 逐字符读取,先找到开头引号,再读到结尾引号为止,中间的所有字符(包括换行符)都会被保留。
cpp
#include<iostream>
#include<string>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::string> quotes(n);
for (int i = 0; i < n; i++)
{
char ch;
// 跳过直到遇到开头引号
while (std::cin.get(ch) && ch != '"');
// 读取引号内的所有内容,直到遇到结尾引号
std::string current;
while (std::cin.get(ch) && ch != '"')
{
current += ch;
}
quotes[i] = current;
}
// 输出验证
std::cout << "读取了 " << n << " 个跨行引号字符串:\n";
for (int i = 0; i < n; i++)
{
std::cout << "--- token " << i << " ---\n" << quotes[i] << "\n";
}
return 0;
}
plaintext
2
"This is a
multi-line
string."
"Second one."
11.4 超大单行数据的优化读取(避免 string 频繁重分配)
说明: 对于超长行(如 MB 级别),预先 reserve 足够空间,避免 getline 内部多次扩容带来的性能损耗。
cpp
#include<iostream>
#include<string>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string huge_line;
// 预分配 2MB 空间,避免 getline 过程中的多次内存重分配
huge_line.reserve(2 * 1024 * 1024);
std::getline(std::cin, huge_line);
// 输出验证
std::cout << "成功读取超大行,长度 = " << huge_line.size() << '\n';
std::cout << "前 50 字符: \"";
for (std::size_t i = 0; i < huge_line.size() && i < 50; ++i)
{
std::cout << huge_line[i];
}
std::cout << "...\"\n";
return 0;
}
plaintext
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()_+~`|}{[]:;?><,./-=
11.5 二进制数据块读取(极罕见,如读取自定义格式文件头)
说明: cin.read(buf, size) 精确读取 size 字节,cin.gcount() 返回实际读取字节数。
注意:需要确保流以二进制模式打开(文件重定向时由系统处理)。
cpp
#include<iostream>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
const std::size_t HEADER_SIZE = 8;
std::vector<char> buffer(HEADER_SIZE);
// 精确读取 8 字节
std::cin.read(buffer.data(), HEADER_SIZE);
// gcount() 返回实际读取的字节数(可能少于请求数,如遇到 EOF)
std::streamsize bytesRead = std::cin.gcount();
std::cout << "期望读取 " << HEADER_SIZE << " 字节数,实际读取 " << bytesRead << " 字节数\n";
// 十六进制 dump 输出
std::cout << "十六进制 dump:\n";
for (std::streamsize i = 0; i < bytesRead; i++)
{
unsigned char byte = static_cast<unsigned char>(buffer[i]);
std::cout << std::hex << (byte < 16 ? "0" : "") << (int)byte << " ";
}
std::cout << std::dec << '\n';
return 0;
}
plaintext
HEAD1234
11.6 多次 cin >> 与 getline 安全切换的范式
说明: 每次从 cin >> 切换到 getline 之前,必须用 ignore 清除残留换行符。本例演示了两次切换的完整流程。
cpp
#include<iostream>
#include<string>
#include<limits>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
// 第一次切换前:清除残留换行
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::string title;
std::getline(std::cin, title); // 读取标题行
std::vector<int> a(n);
for (int i = 0; i < n; i++)
{
std::cin >> a[i];
}
// 第二次切换前:清除残留换行
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::string footer;
std::getline(std::cin, footer); // 读取尾部行
// 输出验证
std::cout << "n=" << n << ", m=" << m << '\n';
std::cout << "title = \"" << title << "\"\n";
std::cout << "array:";
for (int i = 0; i < n; ++i) std::cout << ' ' << a[i];
std::cout << '\n';
std::cout << "footer = \"" << footer << "\"\n";
return 0;
}
plaintext
3 99
Graph Theory Problem
10 20 30
End of Case
11.7 流状态恢复的终极防御(应对交互式/脏数据输入)
说明: 面对可能包含非数字的脏数据,必须同时处理 fail 和 eof 状态。clear 恢复流状态,ignore 跳过错误行。
cpp
#include<iostream>
#include<string>
#include<limits>
#include<vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::vector<int> nums;
int x;
int targetCount = 3; // 目标读取 3 个有效整数
while (targetCount > 0)
{
if (std::cin >> x)
{
// 读取成功
nums.push_back(x);
--targetCount;
}
else
{
// 检查是否提前遇到 EOF
if (std::cin.eof())
{
break;
}
// 清除 fail 状态,跳过当前行剩余内容
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
// 输出验证
std::cout << "最终有效数据:";
for (std::size_t i = 0; i < nums.size(); ++i)
{
std::cout << ' ' << nums[i];
}
std::cout << '\n';
return 0;
}
plaintext
abc
10
xyz
20
30
11.8 EOF 边界安全的 ignore 封装
说明: 当 cin >> 读完最后一个数字后直接到达 EOF(末尾无换行符)时,调用 ignore 会触发 failbit,导致后续 getline 失败。封装 safe_ignore,仅在流未处于 EOF 状态时执行忽略操作。
cpp
#include<iostream>
#include<string>
#include<limits>
#include<vector>
#include<utility>
// 安全 ignore: 防止在 EOF 边界污染流状态
void safe_ignore(std::istream& is)
{
if (!is.eof())
{
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::vector<std::pair<int, std::string>> records;
int id;
while (std::cin >> id)
{
// 使用 safe_ignore 替代原生 ignore,防止 EOF 时触发 failbit
safe_ignore(std::cin);
std::string desc;
if (std::getline(std::cin, desc))
{
if (!desc.empty() && desc.back() == '\r')
{
desc.pop_back();;
}
records.push_back({id, desc});
}
else
{
// 处理只有 id 没有描述的边界情况
records.push_back({ id, "[NO_DESC]" });
}
}
// 输出验证
std::cout << "读取了 " << records.size() << " 条记录:\n";
for (std::size_t i = 0; i < records.size(); ++i) {
std::cout << " id=" << records[i].first
<< ", desc=\"" << records[i].second << "\"\n";
}
return 0;
}
plaintext
1 Apple
2 Banana
3
11.9 高性能行内解析(C++17 std::from_chars 替代 istringstream)
说明: 在海量数据(10510^5105 行级别)下,istringstream 的构造开销可能导致 TLE。C++17 的 std::from_chars 是零分配、无异常、不依赖 locale 的高性能解析方案。
cpp
#include<iostream>
#include<string>
#include<vector>
#include<charconv> // C++ 17 高性能字符转换
#include<system_error> // std::errc
#include<limits>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::vector<std::vector<int>> matrix(n);
for (int i = 0; i < n; i++)
{
std::string line;
std::getline(std::cin, line);
if (!line.empty() && line.back() == '\r')
line.pop_back();
const char* ptr = line.data();
const char* end = ptr + line.size();
while (ptr < end)
{
// 手动调过空白符(from_chars 不会自动跳过)
while (ptr < end && (*ptr == ' ' || *ptr == '\t')) ptr++;
if (ptr >= end) break;
int val = 0;
// from_chars 解析整数,返回解析结果和下一个位置
auto [next_ptr, ec] = std::from_chars(ptr, end, val);
if (ec == std::errc{})
{
matrix[i].push_back(val); // 解析成功
ptr = next_ptr;
}
else
{
ptr++; // 跳过无法解析的字符
}
}
}
// 输出验证
std::cout << "高性能解析结果 (" << n << " 行):\n";
for (int i = 0; i < n; ++i)
{
std::cout << " row[" << i << "] (" << matrix[i].size() << " 个):";
for (std::size_t j = 0; j < matrix[i].size(); ++j)
{
std::cout << ' ' << matrix[i][j];
}
std::cout << '\n';
}
return 0;
}
plaintext
3
10 20 30 40
5 6 7
100 200 300 400 500
11.10 noskipws 标志的 RAII 安全切换与恢复
说明: noskipws 会永久改变 cin 的格式标志,若忘记恢复会导致后续 cin >> 不再跳过前导空白。使用 RAII 守卫自动保存和恢复流标志。
cpp
#include<iostream>
#include<string>
#include<vector>
#include<iomanip>
// RAII 风格的流标志保存与恢复器
class IosFlagSaver
{
public:
explicit IosFlagSaver(std::ios& stream)
: stream_(stream), saved_flags_(stream.flags()) { }
~IosFlagSaver() { stream_.flags(saved_flags_); } // 析构时自动恢复原始标志
IosFlagSaver(const IosFlagSaver&) = delete;
IosFlagSaver& operator=(const IosFlagSaver&) = delete;
private:
std::ios& stream_;
std::ios::fmtflags saved_flags_;
};
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::string raw_data;
{
// 进入局部作用域,创建 RAII 守卫
IosFlagSaver guard(std::cin);
std::cin >> std::noskipws; // 关闭跳过空白
// 逐字符读取(包括空格和换行),直到读满n个字符
for (int i = 0; i < n; i++)
{
char ch;
if (std::cin >> ch)
{
raw_data += ch;
}
}
// 离开作用域,guard析构,cin 的 skipws 标志被自动恢复
}
// 验证标志已恢复:cin >> 会正常跳过前导空白
int trailing_num;
if (std::cin >> trailing_num)
{
std::cout << "成功读取尾部数字: " << trailing_num << '\n';
}
// 输出验证
std::cout << "原始字符数据 (长度 " << raw_data.size() << "):\n";
std::cout << " \"" << raw_data << "\"\n";
std::cout << " Hex dump:";
for (char c : raw_data)
{
std::cout << " 0x" << std::setw(2) << std::setfill('0')
<< std::hex << (int)(unsigned char)c;
}
std::cout << std::dec << '\n';
return 0;
}
plaintext
12
A B C D E F
999
11.11 全局 CRLF 防御型 getline 及复杂混合输入范式
说明: 封装 ultimate_getline 和 ultimate_ignore,同时处理 CRLF 残留和 EOF 边界,适用于最复杂的混合输入场景。
cpp
#include<iostream>
#include<string>
#include<limits>
#include<vector>
#include<utility>
// 防御型 getline: 自动处理 CRLF 和空行
std::istream& ultimate_getline(std::istream& is, std::string& t)
{
std::getline(is, t);
if (!t.empty() && t.back() == '\r')
{
t.pop_back();
}
return is;
}
// 防御型 ignore: 结合 EOF 检查
void ultimate_ignore(std::istream& is)
{
if (!is.eof())
{
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T;
std::cin >> T;
ultimate_ignore(std::cin); // 消除 T 后的换行
for (int t = 1; t <= T; t++)
{
// 1. 读取用例标题(可能含空格)
std::string title;
ultimate_getline(std::cin, title);
// 2. 读取 N 和 K
int n, k;
std::cin >> n >> k;
ultimate_ignore(std::cin); // 消除 N 和 K 后的换行
// 3. 读取 N 个整数
std::vector<int> a(n);
for (int i = 0; i < n; i++)
{
std::cin >> a[i];
}
ultimate_ignore(std::cin); // 消除数组行末尾的换行
// 4. 读取一段自由文本描述(可能为空行)
std::string description;
ultimate_getline(std::cin, description);
// 输出验证
std::cout << "=== Case " << t << " ===\n";
std::cout << "Tile: \"" << title << "\" (len=" << title.size() << ")\n";
std::cout << "n=" << n << ", k=" << k << '\n';
std::cout << "Array:";
for (int i = 0; i < n; i++) std::cout << ' ' << a[i];
std::cout << '\n';
std::cout << "Desc: \"" << description << "\" (len=" << description.size() << ")\n";
}
return 0;
}
plaintext
2
Graph Theory: Shortest Path
3 2
10 20 30
This is a description with spaces.
String Query: Palindrome
4 1
5 6 7 8