☰
C++上机OJ高频考点:输入输出、字符串、排序与调试技巧
2026/10/13 8:54:43 网站建设 项目流程

每年期末的上机考试,总有人对着OJ提交页面怀疑人生:本地跑得好好的,一交上去就是编译错误或者答案错误,最后盯着屏幕发十分钟呆。其实C++上机OJ题翻来覆去就那么几类,题型和考点相对固定。这份基于某邮电类高校信通专业2018年期末上机考试的题目汇总,就是我从当年考场里一套一套整理出来的纯干货,覆盖了输入输出处理、字符串操作、排序结构体、模拟题和简单的类设计等高频考点,适合所有准备C++上机考试的同学参考。

考试环境是机房机器上的本地OJ系统,编译器支持C++11标准,两小时完成全部题目。和笔试最大的区别在于,OJ只看提交代码的最终运行结果,过程分基本没有,一道题要么满分要么零分。所以除了把题做对,还得熟悉OJ的各种脾气——输入输出的细微差别、编译错误的英文提示、运行时错误的常见诱因,这些才是上机考试真正拉开差距的地方。

1. 2018年信通版上机考试全景概览

1.1 考试环境与题型分布

先说考试的基本盘。上机考试采用的是机房预装的本地评测系统,提交后由服务器自动编译运行,用测试用例比对输出结果,只要有一个测试点不过,这道题就一分没有。这一点和平时在IDE里写完随便跑一下完全不同,必须严格按照题目要求的输入输出格式来写代码。

当年这场考试的题目数量是7道,时长两小时,难度呈明显的梯度分布。前2到3题属于签到题,考察最基本的输入输出和分支循环,只要不犯低级错误就能拿分;中间两到三题开始考察字符串处理、排序算法和简单模拟;最后一题基本是压轴题,涉及类与对象或文件流的综合应用,用来控制高分段的区分度。

很多同学容易忽略的是,OJ评测的输入数据往往不是一组,而是多组连续输入。题目里凡是写“输入包含多组测试数据”或者“输入以EOF结束”的,代码就必须写成循环读取的格式。一旦漏掉这个循环结构,评测程序只会拿第一组数据跑一次,后面的测试点全部超时或者输出错误。

1.2 六类必考题型速览

根据2018年信通版题目的整体情况,可以把考点归纳成六个大类。每类题型的出现频率和复习优先级我用表格整理出来了,方便大家对照自查。

题型类别出现频率难度核心考点
基础输入输出必考1~2题低cin/cout或scanf/printf、多组输入、EOF判断、格式控制
分支与循环必考1题低if-else嵌套、循环边界、递推累加
字符串处理高频1~2题中字符数组或string类、子串查找、单词统计、大小写转换
数组与函数高频1题中数组遍历、冒泡/选择排序、函数封装与传参
结构体与排序高频1题中高结构体定义、sort自定义比较器、多关键字排序
类与对象/文件流末题1题高类的封装、运算符重载、文件读写操作

这个分布并不是某一年特有的,近几年信通专业的上机考试基本沿用了同样的结构。也就是说,如果把上面这张表里的每类题都准备到位,哪怕压轴题没完全写出来,前面几类题稳定拿分,成绩也不会差到哪去。真正的备考策略不是去刷多少偏题怪题,而是把这几类高频考点的代码模板记熟、敲熟。

2. 必考点详解:从输入输出到字符串处理

2.1 输入输出格式——最容易被扣分的隐秘角落

输入输出是每套OJ题的第一道关,也是扣分重灾区。常见的问题不是不会写,而是写得不够“精准”。

先说多组输入。题目要求“输入包含多组测试数据,每组占一行”时,标准写法是:

#include <bits/stdc++.h> using namespace std; int main() { int a, b; while (cin >> a >> b) { cout << a + b << endl; } return 0; }

这段代码的核心在于while (cin >> a >> b):当输入流到达文件末尾时,cin的布尔值转为false,循环自动结束。用scanf也一样,写成while (scanf("%d %d", &a, &b) == 2)或者判断!= EOF。

另一种常见格式是“第一行为一个整数N,表示测试数据的组数,接下来N行每行……”。这种题反而容易出错——只读一组就退出循环,或者多读了N后面的空白字符。正确写法是:

int n; cin >> n; while (n--) { // 处理每组数据 }

注意细节:读完N之后如果下一行是字符串,可能需要先getline吃掉残留的换行符。这是C++上机最常见的坑之一,后面会专门讲。

然后是输出格式。OJ对输出的比对是逐字符匹配的,哪怕多一个空格、少一个换行都会判错。题目要求“输出占一行,每个数之间用一个空格隔开”时,有人习惯性地在每次循环末尾都输出空格,结果最后一个数后面多了一个空格,WA(答案错误)没跑。控制分隔符的经典写法是:

for (int i = 0; i < n; i++) { if (i) cout << " "; // 不是第一个数就补空格 cout << arr[i]; }

另外,浮点数输出常常要求保留小数点后两位,格式控制用fixed << setprecision(2),头文件是<iomanip>。如果题目没明确要求保留小数,就不要画蛇添足地加fixed,否则输出格式可能不符。

2.2 字符串处理——上机题的第一大“送分题”与“送命题”

字符串处理类题目在信通版上机考试里几乎年年出现。以前的老教材还在用char[]字符数组,但考试环境支持C++11标准,直接使用string类会省太多事。

有一道典型的字符串题是这样的:输入一行英文句子,要求统计其中单词的个数,单词之间以一个或多个空格分隔。这类题的常见错误是把“空格数加一”当成单词数,遇到前后有空格或者多个连续空格的情况就踩坑。正确的思路是遍历整个字符串,用状态切换的方式计数:

#include <bits/stdc++.h> using namespace std; int main() { string s; getline(cin, s); // 注意:cin >> s 读不了空格 int cnt = 0; bool inWord = false; for (char c : s) { if (c == ' ') { inWord = false; } else if (!inWord) { inWord = true; cnt++; } } cout << cnt << endl; return 0; }

这段代码用getline(cin, s)读入整行文本,cin >> s遇到空格就会停下来,这是初学者最容易踩的坑。inWord这个布尔变量用来标记当前是否处于一个单词内部,只有从非单词状态进入单词状态的瞬间才计数。这样无论句子前后有没有空格、中间有多少连续空格,统计结果都是正确的。

另一个高频考点是字符串反转或大小写转换。反转单个字符串可以原地交换字符,也可以用reverse(s.begin(), s.end())一行搞定。但如果题目要求“反转每个单词,但不改变单词之间的顺序”,就要先按空格分割,再分别反转每个子串。

大小写转换有一个很实用的ASCII码技巧:大写字母转小写,只需要加上32;小写转大写,减去32。判断时用c >= 'a' && c <= 'z'。不过更稳妥的写法是使用<cctype>库里的tolower()和toupper()函数,这两个函数会自动处理字符范围,不会因为字母边界问题而出错。

字符串截取和查找也是常客。find()函数能找到子串第一次出现的位置,没找到时返回string::npos。这里必须注意:判断条件是if (pos == string::npos),而不是if (pos == -1)。虽然npos的值通常就是-1的无符号版本,但直接比较-1在某些编译器下会给出警告,考试时看到警告别慌,但最好还是用标准写法。

还有一个容易被忽略的问题:getline读取完一行后,如果后面再读整数,或者反过来读完整数再读字符串,中间残留的换行符会导致读入错位。解决办法是读完整数后用cin.ignore()清掉换行符,或者统一用getline读入再手动解析。考试时如果发现自己输出完全对不上,先检查是不是输入读取就发生了错位。

3. 核心算法考点:排序、模拟与结构体应用

3.1 排序问题——不只是sort函数这么简单

排序题是信通版上机考试的常青树,几乎每年都有一道。考察重点不是排序算法本身——毕竟std::sort一分钟就写完了——而是排序规则的定制能力。

比如经典的成绩排序题:输入若干学生的姓名和成绩,要求按成绩从高到低排序,成绩相同则按姓名字典序从小到大排序。用结构体加sort自定义比较器,代码非常简洁:

#include <bits/stdc++.h> using namespace std; struct Student { string name; int score; }; bool cmp(const Student& a, const Student& b) { if (a.score != b.score) return a.score > b.score; // 成绩降序 return a.name < b.name; // 姓名升序 } int main() { int n; cin >> n; vector<Student> stu(n); for (int i = 0; i < n; i++) { cin >> stu[i].name >> stu[i].score; } sort(stu.begin(), stu.end(), cmp); for (auto& s : stu) { cout << s.name << " " << s.score << endl; } return 0; }

这里有两个细节需要强调。第一个,比较器cmp必须返回严格的“严格弱序”关系——也就是a排在b前面时返回true。如果排序规则里存在逻辑漏洞,比如比较器写出了return a.score >= b.score,sort的行为就是未定义的,轻则排序结果不对,重则直接运行时报错。第二个,在比较器参数里用const Student&引用传参,避免结构体拷贝的开销。虽然这里结构体体积小影响不大,但养成这个习惯在后面的OJ题里会受益。

如果题目要求不使用sort而是手写排序算法,最稳妥的是选择排序或冒泡排序。这些老算法本身效率比不上快排,但对于OJ题目的数据规模(通常n不超过1000)完全够用。写手写排序时注意交换操作的边界,别让数组越界。

3.2 模拟类题目——把题目翻译成代码的关键套路

所谓模拟题,就是题目描述某一个过程,比如“约瑟夫环”“日期计算”或“进制转换”,要求按步骤把过程用代码实现出来。这类题目本身不涉及高深的算法知识点,但非常考验把自然语言翻译成代码的能力。

日期计算题是模拟题中的经典款。常见问法是:给定某年某月某日,输出这是这一年的第几天。思路是先把每个月的天数存进数组,然后逐月累加。注意闰年的判断:(year % 4 == 0 && year % 100 != 0) || year % 400 == 0。这个判断条件几乎每年都有人写错,漏掉year % 100 != 0或者把||和&&的优先级搞混。

约瑟夫环这类循环模拟题的通用解法是维护一个数组标记哪些位置的人已经出列,用一个指针循环移动。注意当指针到达数组末尾时要回到开头,也就是取模操作pos = (pos + 1) % n。这个取模操作也是很多人的混乱点:循环走指针是先加一再取模,还是先取模再加一?多调试几组小数据就清楚了。

模拟题的另一个核心套路是把复杂问题拆成多个小函数。比如进制转换题,可以先写一个“将十进制数转换成k进制字符串”的函数,再写一个“把k进制字符串转换成十进制数”的函数,主程序里只负责调用。这种做法既降低写错概率,也方便局部调试。考试时不要嫌弃函数调用的代码量,分而治之才是上机题的正确打开方式。

3.3 结构体与文件流——贴近真实开发的考点

结构体在信通专业的上机题里往往不是单独出现的,而是配合排序或链表操作一起考察。前面的成绩排序已经展示了结构体的基本用法,这里补充一个关键但容易被忽略的知识点:结构体变量的整体赋值。

两个相同类型的结构体变量可以直接互相赋值,比如stu[1] = stu[0];,默认是浅拷贝。对于C++ string类型的成员,这种拷贝是安全的;但如果结构体里有指针成员,浅拷贝就会出现两个变量指向同一块内存的问题。信通的专业课后续会学数据结构,上机题里通常不会考到深拷贝那么深,但了解这个区别能帮你排查奇怪的运行结果。

文件流操作是压轴题的常客。典型题目是:从文本文件中读取若干行数据,经过处理后写入另一个文件。核心代码框架如下:

#include <bits/stdc++.h> using namespace std; int main() { ifstream fin("input.txt"); ofstream fout("output.txt"); if (!fin.is_open()) { return 1; // 文件打开失败 } string line; while (getline(fin, line)) { // 处理每一行 fout << line << endl; } fin.close(); fout.close(); return 0; }

这里最容易踩的坑是文件路径。考试时题目通常会说明数据文件名称,但不会说路径,默认就在当前目录下。如果代码里写了绝对路径,换一台机器评测就找不到了。所以一律用相对路径,也就是文件名称本身,不要自作聪明地加目录。另外,写完记得close(),尤其是有多个文件操作时,文件句柄不释放会导致后续操作异常。

还有一个小众但考过的点:中文文本的读取。过去的C风格字符数组处理UTF-8中文很容易乱码,用string加getline来做按行处理,情况会好很多。如果确实需要判断中文字符的某个字节,记住UTF-8中每个汉字占3个字节,实测这么判断一般没问题。

4. 调试技巧与考场避坑指南

4.1 万能调试三板斧

上机考试没有断点调试器,至少说很多同学在考场上根本不会用IDE的调试功能。这种情况下,最快的排错手段就是在代码里插入输出语句。

第一板斧:关键位置打印中间变量。比如排序后打印一遍数组,检查排序规则是否正确;模拟题的每个大循环末尾打印当前状态,看数据是不是按预期在变化。考场上不要舍不得删调试输出——确认逻辑正确后删掉重交一次就行。不过要注意,OI赛制和部分OJ系统会屏蔽额外的输出内容,所以提交前一定记得清理干净。

第二板斧:构造小规模测试数据。OJ给的测试样例通常只有一组,覆盖不全。自己构造几组边界数据来测:没有输入的情况、只有一个元素的情况、重复元素的情况、字符串为空的极端情况。很多时候答案错误都是因为没考虑边界条件。

第三板斧:利用返回值判断错误类型。程序运行时直接崩溃,OJ会给出RE(运行时错误)。RE的原因不外乎数组越界、除零、栈溢出、空指针。用二分自查的方式,在代码里加return 0;来缩小崩溃范围——先在最开头加,看是否还RE;不RE再往后挪,最终定位到崩溃点。这个办法虽然笨但非常有效,实测能在五分钟内定位绝大多数RE问题。

4.2 典型编译错误与运行错误速查

编译错误是考场上的常见拦路虎。很多人看到大段英文报错就慌了,其实常见的就那几种,记住关键词就能快速定位。

undefined reference to 'main',链接器找不到主函数,通常是代码里漏写了int main()或者把main拼成了mian。expected ';' before ...,某个语句末尾漏了分号,或者上一行结构不完整。'xxx' was not declared in this scope,变量或函数没有声明,可能是拼写错了、忘记声明变量、或者引用的头文件没包含。

运行错误也有规律。最常见的是段错误Segmentation fault,本质是访问了非法内存地址,通常由数组越界或访问空指针导致。另外就是浮点异常Floating point exception,其实是整数除零,想想代码里有没有可能让分母或者取模的模数变成0。

编译警告里还有一条很坑:比较有符号和无符号整数时会提示comparison between signed and unsigned integer expressions。比如string s; ... s.length()返回无符号类型,拿它和int i比较就可能触发这个问题。上机考试里这种警告一般不影响评测,但如果开了-Werror选项,警告直接升级为错误,编译就不通过了。稳妥做法是把下标变量声明成size_t类型,或者每次使用时强转(int)s.length()。

5. 常见问题排查与备考建议

5.1 上机考试典型问题速查表

我把考场上最常见的现象、可能的原因和对应解法整理成了一张速查表,希望大家上机前花两分钟扫一眼,遇到问题能直接对照定位。

现象可能原因排查思路
本地运行正确,OJ上显示答案错误多组输入没循环读;输出格式多了空格或换行;变量没初始化检查while循环结构,用最简单的样例逐字符比对输出
编译错误:expected initializer before ...结构体或类定义末尾漏了分号检查结构体}后面是否有;
编译错误:stray '\243' in program代码里混入了中文字符或全角标点检查注释和字符串里的中文引号,建议代码注释也写成英文
运行超时TLE算法效率太低;死循环;输入没读完检查循环条件是否更新,n是否递增;排序题确认没有写死循环
运行错误RE数组越界;除零;递归栈溢出检查所有数组下标范围,补边界判断,别让分母出现0
本地输出中文乱码编码格式不兼容上机题的字符串一般没有中文字符,尽量用英文处理

5.2 考前一周的复习建议

如果离考试还有一周,最高效的复习方案不是狂刷新题,而是做三件事。

第一,把本目录里的每一类题的模板代码亲手敲一遍。手和眼是不一样的,看得懂不等于敲得对。尤其是sort自定义比较器、getline消费换行符、文件流读写这几段代码,考场上是没有时间现查语法规则的。敲到条件反射才算真正掌握。

第二,狠抠输入输出的边界条件。随便找一些字符串题,故意在数据前后加空格、中间加连续空位,测试自己的代码是不是还能稳定运行。上机OJ的评测数据里,边界和极端情况往往才是真正拉开分数的位置。这个阶段多花一个小时,考场上就少一次无谓的WA。

第三,做一次限时模拟。在OJ平台上随便找一套题,严格按两小时标准计时,中间不翻教材不查资料,完全模拟考场状态。重点不是做对多少题,而是训练时间分配:签到题15分钟内解决,中等题一道不超过半小时,最后一题如果卡了20分钟还没思路,果断放弃写前面的检查。

最后再分享一个考场小技巧:交卷前留五分钟从头到尾检查一遍代码。主要检查有没有多余的调试输出、数组开得够不够大、有没有把题目的文件名拼错。这五分钟通常能救回好几分。希望这份基于2018年信通版考试整理出来的干货,能帮你在上机考场上少踩坑、多拿分。祝顺利。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询