前几天,有家长找我,说孩子准备考电子学会的C++二级,6月那场考试已经结束了,网上搜到的真题解析要么付费、要么拉群、要么讲得云里雾里。我正好这段时间在用C++写一个“英语老师小助手”,功能很朴素:随机点名、统计默写成绩、给单词表去重排序。每天和数组、字符串、结构体这些打交道,回头翻二级真题的时候发现好几道题本质上就是这类小需求的简化版。
所以这篇就把2024年6月电子学会C++二级的真题过一遍。要说明一下,题目是根据考生回忆整理的回忆版,个别题目的数据范围和描述我做了合理归并,但考察的知识点和解题代码是实打实的。全文会把每道题的题目描述、解题思路、参考代码、易错点全部拆开,适合正在备考二级的中小学生,也适合带编程课的老师,以及想在家辅导孩子又不知道从哪下手的家长。
1. 二级到底考什么,和“英语老师小助手”有什么关系
1.1 先搞清楚考点地图再刷题
电子学会的青少年等级考试C++部分,一级考的几乎全是顺序、分支、循环这些最基础的语法,能把程序写出来、能跑通就差不多了。但二级明显不一样,它默认你已经熟悉了基本语法,开始考察你用代码解决实际问题的能力。
我梳理了一下二级的核心考点,基本是这几块:
- 输入输出:cin、cout的基本用法,包含头文件、格式控制。
- 分支结构:if-else、switch嵌套,多条件判断。
- 循环结构:for、while、do-while,循环嵌套。
- 数组:一维数组、二维数组的存储和遍历,下标越界的坑。
- 字符串:string类型的基本操作,getline读入,字符串分割。
- 结构体:用struct组织多条数据,数组存对象。
- 简单算法:枚举、模拟、计数、求最值、统计。
一级只要会写“顺序往下走”的程序,二级就要求“按条件跳”“反复循环”“批量处理”。一句话概括:一级学语法,二级练思维。很多孩子从一级到二级突然觉得难,就是因为还停留在“照着样例敲代码”的阶段,没有形成“先分析问题、再选数据结构、最后写算法”的思路。
1.2 一个英语老师小助手,几乎覆盖二级全部考点
我为什么强调“英语老师小助手”这件事?因为这个小程序本质上就是二级考题的“成年版”。
它大概做这几件事:
- 用结构体数组存学生姓名和三科默写成绩。
- 按平均分从高到低排序。
- 统计全班平均分、最高分。
- 从名单里随机抽人提问。
你看,这不就是结构体、数组、循环、字符串处理的全套组合吗?2024年6月的二级真题里,有好几道题几乎就是把这些需求“简化一下”再出出来。我甚至觉得,如果你能独立写出一个这样的“英语老师小助手”,二级的编程题基本就稳了。
先把这个小需求记在心里,下面看真题的时候你会发现题目很眼熟。
2. 2024年6月二级真题回忆版与逐题解析
2.1 真题1:偶数统计与最大偶数
题目描述(回忆版): 第一行输入一个整数n,第二行输入n个整数,统计其中偶数的个数,并输出这些偶数中的最大值。如果这n个数里没有偶数,第二行输出-1。
样例输入:
5 1 2 3 4 6样例输出:
3 6这道题是典型的“循环+分支+求最值”三件套,属于二级考试里的送分题。应对这种题,我建议养成一个习惯:能边读边处理就不要把数据先存进数组。因为统计偶数个数和求最大值根本不需要回头访问数据,读一个处理一个,既省空间又省时间。
参考代码:
#include <bits/stdc++.h> using namespace std; int main() { int n, x; cin >> n; int cnt = 0, mx = -1; for (int i = 0; i < n; i++) { cin >> x; if (x % 2 == 0) { cnt++; if (x > mx) mx = x; } } cout << cnt << "\n"; cout << mx << "\n"; return 0; }这里有几个细节要注意。
第一,判断偶数用x % 2 == 0。有同学习惯写成x % 2 == 0,这是对的;但如果写成x % 2 == 1判断奇数,遇到负数就容易出错,因为C++中负数取模的结果可能是负数,比如-3 % 2在多数编译器里结果是-1。所以考试时如果输入可能出现负数,判断偶数一定要写x % 2 == 0。
第二,mx的初始值设置为-1很关键。因为偶数最小可能是0或者2,用-1作为“尚未找到偶数”的标记,最后直接输出初始值就是-1,完美满足题意。
第三,输出格式要求两行,不要图省事写成cout << cnt << " " << mx << endl;,虽然样例看不出问题,但评测是按行比对的,格式不对就是零分。
2.2 真题2:统计单词数量与最长单词长度
题目描述(回忆版): 输入一行英文字符串,只包含大小写字母和空格,单词之间用一个或多个空格分隔。统计这段字符串里有多少个单词,并输出最长单词的长度。
样例输入:
I love programming样例输出:
3 11这道题和英语老师小助手“统计默写单词数量”的需求几乎一模一样。拿到这种题,第一反应应该是用getline整行读入,再想办法分割单词。
参考代码:
#include <bits/stdc++.h> using namespace std; int main() { string s; getline(cin, s); stringstream ss(s); string word; int cnt = 0, maxLen = 0; while (ss >> word) { cnt++; int len = word.size(); if (len > maxLen) maxLen = len; } cout << cnt << " " << maxLen << "\n"; return 0; }这道题最大的坑,不是算法,而是getline和cin混用的问题。
我见过太多考生,前面已经有cin >> n了,后面又写getline(cin, s),结果第一行字符串读进来永远是空的。原因很简单:cin >> n读到换行符会停,但换行符还残留在输入流里,紧接着的getline读到的就是一个空行。
解决办法有两个:在getline之前调用一次getchar()吃掉残留的换行符;或者在读取n之后立刻cin.ignore()。我个人更推荐后者,因为getchar()遇到多平台编译环境时行为可能略有差异。
另外,stringstream的方式对“多个空格分隔”的情况特别友好。哪怕两个单词之间有十个空格,ss >> word也能自动跳过,不用自己写复杂的循环去判断空格。头文件方面,#include <bits/stdc++.h>已经包含了<sstream>,所以不用担心。
2.3 真题3:学生成绩统计与最高平均分
题目描述(回忆版): 第一行输入一个整数n,表示学生人数。接下来n行,每行包含一个学生姓名(不含空格)以及语文、数学、英语三科成绩。要求输出全班三科成绩的总平均分(保留两位小数),再输出平均分最高的学生姓名。如果平均分相同,输出先出现的那一个。
样例输入:
3 Alice 90 85 92 Bob 88 95 80 Cindy 89 91 94样例输出:
89.33 Cindy这道题是“结构体+数组+求最值”的经典组合。二级考试非常喜欢出这类“学生成绩类”题目,因为它既贴近校园场景,又能把结构体、数组、浮点数处理一次全考到。
参考代码:
#include <bits/stdc++.h> using namespace std; struct Student { string name; int score[3]; }; int main() { Student stu[105]; int n; cin >> n; double totalScore = 0; double bestAvg = -1; string bestName; for (int i = 0; i < n; i++) { cin >> stu[i].name >> stu[i].score[0] >> stu[i].score[1] >> stu[i].score[2]; int sum = stu[i].score[0] + stu[i].score[1] + stu[i].score[2]; totalScore += sum; double avg = sum / 3.0; if (avg > bestAvg) { bestAvg = avg; bestName = stu[i].name; } } cout << fixed << setprecision(2) << totalScore / (n * 3) << "\n"; cout << bestName << "\n"; return 0; }这道题有几个高频丢分点,我挨个说一下。
最经典的是整数除法精度丢失。求平均分必须写成sum / 3.0,如果写成sum / 3,两个整数相除会直接截断小数部分,结果就不是真正的平均分了。考场上很多人代码逻辑全对,就是因为少写了一个小数点,平均值算出来差一大截。
然后是输出格式。保留两位小数必须用fixed << setprecision(2),顺序不能反。setprecision(2)单独用是保留两位有效数字,不是两位小数;加上fixed才是固定小数位。这个点考试几乎年年有人在这翻车。
再说平均分相同的情况。题目要求输出最先出现的那个,我们的判断条件用的是if (avg > bestAvg),而不是>=,所以后出现的同名平均分不会覆盖之前的结果,天然满足题意。如果你写成了>=,遇到并列就会输出最后一个,直接丢分。
最后说一下数组空间。二级考试的数据范围一般不会太刁钻,但保险起见,数组尽量开大一点,比如Student stu[10005],并且尽量放在全局区。局部数组开太大,在Windows环境下可能直接爆栈,程序还没跑就崩溃了。
2.4 真题4:矩阵转置
题目描述(回忆版): 输入两个整数n和m,然后输入n行m列的整数矩阵。输出该矩阵的转置矩阵,也就是m行n列,原矩阵第i行第j列的元素,在新矩阵中位于第j行第i列。
样例输入:
2 3 1 2 3 4 5 6样例输出:
1 4 2 5 3 6矩阵转置这道题,考的不是算法,而是对二维数组下标关系的理解。二级考试中出现二维数组题,几乎必考下标转换,所以这道题很有代表性。
参考代码:
#include <bits/stdc++.h> using namespace std; int a[105][105]; int main() { int n, m; cin >> n >> m; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> a[i][j]; } } for (int j = 0; j < m; j++) { for (int i = 0; i < n; i++) { cout << a[i][j] << " "; } cout << "\n"; } return 0; }这道题的核心就一句话:转置就是交换行列下标。原矩阵是n行m列,存的时候外层循环i走n、内层j走m;输出转置矩阵时,要把j放到外层循环,i放到内层循环,输出a[i][j]而不是a[j][i]。
我最想提醒的是:不要一上来就想“原地转置”。如果矩阵是方阵,原地转置确实可以省一个数组,但交换下标时稍不留神就会把已经交换过的元素再换一次,结果整个矩阵乱掉。考试就老老实实开一个二维数组存原矩阵,然后按列输出,既直观又不容易错。
还有就是二维数组的声明位置。把int a[105][105]放到main外面、全局变量的位置,这是考场上的习惯动作。因为局部数组在栈上分配,稍微大一点就容易出问题,全局变量则安全得多。另外,遍历顺序不要搞反:读入一定是行优先,输出转置时才改成列优先,很多同学代码写反了,样例还过不了,就是因为自己把行和列搞混了。
2.5 真题5:文具购买方案数(枚举)
题目描述(回忆版): 老师手上有S元钱,要去买三种文具A、B、C,单价分别为a元、b元、c元。三种文具每种至少要买1件,要求恰好花完S元。问一共有多少种购买方案。
样例输入:
10 2 3 5样例输出:
1样例解释:S=10,a=2,b=3,c=5时,只有“A买1件、B买1件、C买1件”这一种方案,2+3+5刚好等于10。
这道题是典型的枚举法,二级里最常考的算法就是枚举和模拟。枚举的核心思想很简单:把所有可能的情况都试一遍,符合条件就计数。听起来笨,但它在考试里非常好用。
参考代码:
#include <bits/stdc++.h> using namespace std; int main() { int S, a, b, c; cin >> S >> a >> b >> c; int ans = 0; for (int x = 1; x * a < S; x++) { for (int y = 1; x * a + y * b < S; y++) { int rest = S - x * a - y * b; if (rest >= c && rest % c == 0) { ans++; } } } cout << ans << "\n"; return 0; }很多人第一次写这题,会把条件写成if (rest % c == 0),忘了检查rest / c >= 1也就是rest >= c。题目要求每种至少买一件,如果剩余钱数根本不够买一件C,虽然整除但方案不成立,就会多算。所以以后凡遇到“至少一件”“不为零”这类字眼,一定要警惕边界条件。
枚举循环的上界也可以优化一下。比如x的循环条件是x * a < S,因为要给B和C至少各留一件钱;如果写成x * a <= S,最后还要在循环体里做很多无用判断。虽然这道题数据范围小,暴力也能过,但好的习惯能让你在数据范围突然变大时不至于卡死。
如果S和单价数据范围特别大,比如S达到一万甚至十万,两层枚举可能就超时了。那时候可以考虑只枚举x,然后对y的个数或者对剩余钱数做数学推导。但二级考试数据范围一般不会这么大,重点还是先把暴力枚举写对写稳。
3. 考场实战:从审题到调试的时间分配与避坑
3.1 拿到试卷先干三件事
很多同学拿到题就开始唰唰敲键盘,这是考场大忌。我的建议是,拿到试卷后先花三五分钟把所有题都读一遍,然后做三个判断:哪道题最简单先拿分,哪道题代码量最大放中间,哪道题完全没思路最后再啃。
这个策略在考试里非常实用。二级一般就是5道编程题,时间不算宽裕,如果一上来死磕某道难题,后面简单题的分数全丢了,那就太亏了。我认识一个A同学,平时水平很好,就是考试时非要按顺序做,结果第二题卡了半小时,后面三道简单题时间不够,最后只做出来一道半。考试比的不是谁解难题快,而是谁总分高。
我的顺序一般是:先把“统计类”题目做掉,因为逻辑简单、代码量小;再做“字符串处理”类题目,这类题坑多在细节,需要耐心;最后做“枚举/模拟”类题目,这类题需要一点推导,放中间或后面都行。
3.2 高频易错点速查表
有些错误,真的年年有人犯。我整理了一个表格,备考的时候建议自己对着检查一遍。
| 错误类型 | 典型表现 | 后果 | 解决方案 |
|---|---|---|---|
| 整数除法精度丢失 | 用sum / 3算平均分 | 小数部分被截断,结果错 | 至少有一个操作数是小数,如sum / 3.0 |
| 输出格式不符 | 该换行没换行,多输出了空格 | 评测机显示格式错误,零分 | 严格对照样例,行尾空格一般没关系,但换行必须有 |
| 数组越界 | 循环写到i < n写成了i <= n | 可能读到脏数据,或者直接RE | 下标从0开始,上界是n-1 |
| getline被跳过 | cin >> n后立刻getline | 字符串读空,逻辑错误 | 加cin.ignore()或getchar()吃掉换行 |
| 结构体数组没开够 | 开了stu[100],输入却有1000人 | 越界访问,程序崩溃 | 数据范围不确定时尽量开大,并放全局区 |
| 判断条件漏边界 | 枚举时只判断整除,没判断至少1件 | 方案数多算 | 多读题,把“至少”“最多”这些字眼划出来 |
忘写return 0; | 程序末尾没有return 0 | 标准不严的编译器不报错,但可能扣分 | 每次写完代码最后补上 |
这份表格建议备考学生考前看一遍,比临时翻教材管用得多。
3.3 测试样例的“歪招”
程序写完、样例也通过了,并不代表真的没问题。我强烈建议大家养成三个测试习惯。
第一个习惯是“边界值测试”。题目说n不超过10000,那就测n=1和n=10000;题目说只有一个小写字母,那就测输入是单个字符的情况。很多隐藏问题都是边界条件引起的。
第二个习惯是“自己手算额外样例”。不要只满足于题目的样例。比如做统计题,你多写一组包含“没有偶数”“单词之间多个空格”“所有学生成绩相同”的数据,能提前发现很多逻辑漏洞。
第三个习惯是“注释法排查”。如果程序结果不对,不要盯着屏幕发呆,先在关键位置加cout << "中间结果",把每一步的变量值打印出来,跑一遍就能定位是循环边界错了、判断条件错了,还是某个变量被意外覆盖了。排除完再删掉这些调试输出。这个方法听起来土,但真的比靠眼睛盯代码高效十倍。
4. 从真题回归实战:英语老师小助手的完整代码与备考建议
4.1 把真题需求缝进真实工具
前面我说过,英语老师小助手这个工具和二级真题的考点高度重合。这一节我把它的核心骨架写出来,大家会发现,它就是真题3的“加强版”。
参考代码:
#include <bits/stdc++.h> using namespace std; struct Student { string name; int scores[5]; double avg; }; int main() { Student stu[10005]; int n, m; cin >> n >> m; // n个学生,m次默写 for (int i = 0; i < n; i++) { cin >> stu[i].name; int sum = 0; for (int j = 0; j < m; j++) { cin >> stu[i].scores[j]; sum += stu[i].scores[j]; } stu[i].avg = double(sum) / m; } // 按平均分从高到低排序(选择排序) for (int i = 0; i < n - 1; i++) { for (int j = i + 1; j < n; j++) { if (stu[j].avg > stu[i].avg) { swap(stu[i], stu[j]); } } } for (int i = 0; i < n; i++) { cout << stu[i].name << " " << fixed << setprecision(2) << stu[i].avg << "\n"; } return 0; }这个程序用到的结构体、二维数组、双重循环、排序、格式化输出,全部都是二级考点。如果你能看懂这段代码,甚至可以自己扩展一个“随机点名”功能:用rand() % n随机选一个下标,输出对应的名字。
这就是我常说的“以练代学”。只刷题容易忘,但如果你脑子里有一个真实项目,每做一个功能就对应一个知识点,知识就不容易丢。比如单词统计题对应小助手的“输入默写内容”,成绩排序题对应小助手的“排名榜”,矩阵转置对应小助手的“表格行列转换”。
4.2 备考二级的三条实在建议
第一条建议:每道题先写伪代码再写C++。很多孩子喜欢直接开写,写一半发现思路不对,全部删掉重来,浪费时间还打击信心。正确做法是先拿纸笔列出“输入什么、要算什么、输出什么”,用中文把逻辑顺序写清楚,再翻译成C++。二级题目普遍不长,这一步花不了三分钟,但能让代码一次写对。
第二条建议:不要盲目追求“高端写法”。二级考试改卷看的是结果和基础代码能力,不是看你用了多高级的STL。有的同学非要用map、set、vector,结果自己还没掌握,写出来一堆编译错误。稳定用数组、结构体、字符串、循环,把基础写法练扎实,比什么都强。当然,如果你对vector很熟练,用也没问题,关键是选自己最熟悉的方式。
第三条建议:刷题之后一定要复盘。做完一道题,对照标准答案看看自己的代码有哪些可以简化的地方。比如矩阵转置,你是不是读入之后就立刻输出了?如果这样能行,当然可以;但很多时候题目会要求先存起来再做二次处理,这时候不读入存储直接输出就会出问题。复盘不是浪费时间,是让你把一道题的思路变成一类题的思路。
最后再说几句
我写英语老师小助手的时候,最深的体会是:编程题目看着五花八门,其实翻来覆去就是“存储数据、遍历数据、处理数据、输出数据”这几板斧。二级真题里那一堆统计、排序、枚举的题目,本质都是这么一回事。
最后再分享一个小技巧:考试的时候,可以用注释先写框架。
// 1. 读入 // 2. 处理 // 3. 输出先把三大块写出来,再往里面填代码,思路永远不会乱。这个方法我自己用了很多年,写任何程序都好使。希望这份真题解析能帮你少走点弯路,祝备考二级的同学都能一次通过。