C++上机考试冲刺(一)—C++17基础与输入输出
这组文章面向三题制的 C++ 上机考试,目标不是重新学习完整的 C++ 语言,而是在一周内恢复“读题后能够迅速写出、编译并调通程序”的能力。正文统一使用 C++17、标准头文件和显式 std:: 前缀;C++20 语法只放在可选补充中,不作为考场模板的依赖。
系列共六篇:
- C++17 基础与输入输出;
- 字符串、进制、位运算与字节流;
- STL 容器与常用数据结构;
- 排序、查找与二分模板;
- 双指针、滑动窗口与高频组合模式;
- 设计题、业务模拟与三题实战。
本篇先解决最基础、也最容易造成无谓失分的问题:程序骨架、类型选择、函数参数、输入解析、输出格式和边界处理。
一、考场程序的基本结构
完整程序至少包含头文件、辅助函数和 main()。建议把题目的核心逻辑写成独立函数,让 main() 只负责输入、调用和输出。这样既方便本地调试,也能快速迁移到只要求实现函数的 LeetCode 式平台。
1 | // verify: cpp17 |
输入:
1 | 5 |
输出:
1 | min=-5 |
这段程序体现了几条可直接复用的原则:
main()检查输入是否成功,并在调用analyze()前保证数组非空;- 只读且可能较大的参数使用
const T&,避免复制; - 求和的初始值写成
0LL,使std::accumulate()从一开始就按long long累加; - 平均值计算前显式转换为
double,避免整数除法; - 输出使用
\n。只有确实需要立即刷新缓冲区时才使用std::endl。
std::ios::sync_with_stdio(false) 可以关闭 C++ 流与 C 标准 I/O 的同步。使用它以后,不要在同一个程序中混用 std::cin、std::cout 与 scanf()、printf(),否则输出顺序可能不符合预期。
二、考场常用类型
算法题首先要根据数据范围选择类型。下面不是完整的语言类型表,而是上机考试最常用的选择。
| 类型 | 常见用途 | 主要注意事项 |
|---|---|---|
int |
下标、计数、绝对值不超过约 20 亿的整数 | 两个 int 相乘会先按 int 计算,结果可能在赋给 long long 前已经溢出 |
long long |
总和、乘积、距离、时间戳 | 通常至少为 64 位,但仍要检查题目上界 |
double |
平均值、比例、允许误差的计算 | 不要直接用 == 判断复杂浮点计算结果 |
char |
单个字节字符 | char 是否有符号由实现决定,传给 <cctype> 函数前要谨慎转换 |
bool |
条件和状态标记 | 输出默认是 0 或 1,可使用 std::boolalpha 输出 false 或 true |
std::size_t |
容器大小和下标 | 是无符号类型,不要让它参与可能得到负数的计算 |
最常见的溢出错误如下:
1 | int width = 100000; |
如果题目给出 n <= 10^5、单个数值 value <= 10^9,总和的上界可达到 10^14,应直接使用 long long。
三、初始化、auto、引用与 const
1. 声明时完成初始化
局部基本类型不要只声明、不赋值:
1 | int count = 0; |
容器可以使用列表初始化:
1 | std::vector<int> values{3, 1, 4}; |
2. auto 适合消除重复类型,不适合隐藏关键类型
1 | auto iterator = values.begin(); |
当整数宽度直接关系到正确性时,仍应明确写出 long long、std::uint64_t 等类型。
范围 for 循环要特别注意是否复制元素:
1 | for (int value : values) { // 读取副本 |
对 int 这类小类型,按值读取通常更直接;对 std::string、结构体等较大对象,优先使用 const T&。
3. 函数参数的快速选择
| 目的 | 推荐形式 |
|---|---|
| 读取小对象 | int value |
| 读取大对象且不修改 | const std::vector<int>& values |
| 修改调用者对象 | std::vector<int>& values |
| 对象所有权需要转移 | 本系列不作为应试重点,先按值传递并在必要时移动 |
返回局部 std::vector、std::string 或结构体时,通常直接按值返回即可,不要为了“避免复制”返回局部变量的引用。
四、四种高频输入形式
1. 固定数量的空白分隔数据
operator>> 会自动跳过空格、换行和制表符,因此不必关心数据是否全部写在同一行。
1 | int n = 0; |
2. 多组测试数据
题目第一项给出测试组数 t 时,外层循环只负责重复调用单组逻辑:
1 | void solveOneCase() { |
把每组状态定义在 solveOneCase() 内,可以避免上一组数据污染下一组。
3. 读取到文件结束
题目没有给出记录数量时,可以把读取操作本身作为循环条件:
1 | long long left = 0; |
不要写成 while (!std::cin.eof())。文件结束标志通常要在一次读取失败后才会被设置,这种写法容易让最后一组数据被重复处理。
4. 读取完整一行
std::getline() 会保留行内空格,但不会保留行末换行符:
1 |
|
std::cin >> n 会把换行符留在输入缓冲区。如果随后直接调用 std::getline(),第一次得到的往往是空字符串。ignore() 的作用是丢弃这一行剩余的字符。
五、下标循环与边界
容器的半开区间是 [begin, end):起点包含在范围内,终点指向最后一个元素之后。下标区间同样优先写成 [0, n)。
1 | for (std::size_t i = 0; i < values.size(); ++i) { |
反向遍历无符号下标时,不要写 i >= 0:
1 | for (std::size_t i = values.size(); i-- > 0;) { |
更简单的场景可以使用反向迭代器:
1 | for (auto it = values.rbegin(); it != values.rend(); ++it) { |
当必须计算 right - left 且结果可能为负数时,使用有符号整数保存逻辑下标,并在访问容器前确认范围合法。
六、输出格式
1 |
|
流格式会持续生效。输出十六进制后,如果后面仍要输出十进制,应显式切回 std::dec;设置 std::fixed 和精度后也要确认后续输出是否仍需要该格式。
七、代表题与思考过程
代表题一:批量读数统计
篇首完整程序就是本题答案。解题过程如下:
- 输入规模决定使用
std::vector<int>保存读数; - 单个值使用
int,总和使用long long; - 非空数组才能解引用
std::minmax_element()的结果; std::accumulate()的初始值决定累加类型;- 平均值需要浮点除法和固定精度输出。
时间复杂度为 O(n),保存输入需要 O(n) 空间。如果题目只要求最小值、最大值、总和与平均值,也可以在读取时同步统计,把额外空间降为 O(1)。
代表题二:阈值计数
给定一组整数和阈值,统计大于等于阈值的元素数量。核心函数可以直接使用标准算法:
1 |
|
Lambda [limit] 按值捕获阈值。函数只遍历一次数组,时间复杂度为 O(n),额外空间复杂度为 O(1)。
代表题三:逐行处理日志
如果每条日志占一整行且字段之间可能包含空格,应使用 std::getline()。处理流程通常是:
1 | std::string line; |
不要在没有确认题意时自动跳过空行:有些题目把空字符串视为合法数据。上面的判断只适用于题目明确说明空行无效的场景。
八、常见错误与调试顺序
1. 编译错误
依次检查:
- 是否包含了声明该类型或函数的标准头文件;
- 是否漏写
std::; - 圆括号、花括号、方括号和分号是否成对;
- 函数声明、定义与调用的参数类型是否一致;
- 使用的特性是否属于 C++17。
2. 结果错误
使用最小样例逐项打印中间状态,重点检查:
- 空输入、单元素、全相等、全为负数;
0、最大值和最小值;- 循环是否少处理或多处理一个元素;
- 除法发生在转换为浮点数之前还是之后;
- 乘法和累加是否已经溢出。
3. 超时
先判断算法复杂度是否合适,再考虑 I/O。sync_with_stdio(false) 无法挽救本应为 O(n log n) 却写成 O(n^2) 的算法。
九、考前速查清单
1 |
|
- 总和或乘积可能超过
int:使用long long,乘法写1LL * a * b; - 整数平均值需要小数:除法前转换为
double; std::accumulate()需要 64 位结果:初始值写0LL;operator>>后接std::getline():先处理残留换行;- 读到文件结束:
while (std::cin >> value); - 容器范围:优先使用
[begin, end)和[0, n); - 核心逻辑写入函数,
main()只负责输入输出; - 提交前至少测试空边界、单元素和最大数据范围。
十、C++20 可选补充
C++20 提供 std::ssize(container),可以直接获得有符号的容器大小。在本系列的 C++17 基线中,仍使用 container.size(),并避免在无符号类型上做可能为负的运算。
十一、延伸练习
- LeetCode 1480:一维数组的动态和:练习循环、数组和返回值;
- LeetCode 724:寻找数组的中心下标:练习
long long总和与单次遍历; - LeetCode 58:最后一个单词的长度:练习字符串边界与反向扫描。


