我去年带一个学生考完202503批次的GESP C++二级,出来以后复盘题目,最大的感受是:二级真不是“背一背语法就能过”的级别了。考场上三十来道选择判断加四道编程题,题目本身看着都不长,但设的坑一个接一个,尤其是编程大题,全是对“能不能把循环、数组、字符串组合起来解决实际问题”的考查。这篇就直接拿202503二级里几个典型题目(按考纲风格整理的示例题,不是原卷逐字)做拆解,把每道题背后的考点、思路推导、参考写法、考场翻车点全部讲透。看完以后你会发现,二级的编程题是有明确套路的,就三类:按位拆数字、数组统计排序、字符串边界切分,再有就是二维数组的边界处理。这篇适合正在备考GESP二级的学员、带考级的老师,以及想拿C++入门练代码基本功的自学者。
1. 二级考点全景拆解
1.1 202503场次的出题风格与难度曲线
202503这一批次的试卷,选择题部分整体比较温和。变量类型转换、运算符优先级、if和switch选哪个、for循环的执行流程,这些是绝对主力。和往年对比,这次选择题里“读代码写结果”的题占比明显升高,也就是说,你光知道语法不够,还得能手动模拟程序执行路径。有不止一道题是在键盘上根本输不出正确结果,但纸笔推演反而更快的类型。
编程大题是拉开差距的关键。四道题从易到难分布大概是:第一题纯循环计数,第二题是数组标记+排序,第三题字符串单词处理,第四题二维数组边界求和。没有考到太偏的算法,也没有考需要递归或者复杂排序算法的题,但第四题对“去重”和“边界”的考察很细,不少考生栽在这里。整体难度曲线比较友好,第一题基本送分,第二题认真做能拿大半,第三题开始考验代码组织能力,第四题是真正的分水岭——能做对的人,二级稳过;做不对的,大概率在及格线边缘。
我的判断是,202503这套题出得“中规中矩但暗藏杀机”。它不像三级那样考递归、结构体、文件读写这些新东西,但把二级考纲里的老考点挖得很深。你如果只是会写单个循环、会定义一个数组,但没有把“循环+数组+字符串”组合起来解决具体问题的能力,做题就会觉得每道题都眼熟,写起来处处卡壳。
1.2 二级考纲核心考点地图
要精准备考二级,先得在脑子里建一张考点地图。GESP二级覆盖的知识范围大致是:基本数据类型与变量作用域、输入输出、if-else与switch分支、for与while循环、一维数组的定义与遍历、字符串的输入输出与常用操作、函数的基本定义与调用,以及简单二维数组。二级不考指针、不考结构体、不考文件操作,但有一个容易被忽略的要求——代码需要有一定的结构性,不能全部堆在main里。
从202503的题目来看,考纲里有几个点被放大了:
第一,复合条件的判断。比如要统计一个区间里满足多个条件的数,每个数都要拆出每一位做判断,这就同时用到循环嵌套、整除取模、逻辑表达式。二级的难点从来不是某一个语法点,而是多个语法点的组合。
第二,数组的“标记”用法。很多人学数组只学会了“存数据”和“遍历求和”,但二级很爱考“用数组记录状态”——比如某个数字出现没出现过、某个值被使用了几次。这个思想是后面桶排序、哈希表、并查集的地基,二级就要求你用最简单的方式体现。
第三,字符串的边界处理。C++里cin遇到空格就停了,想读一整行要用getline。而遍历字符串时,单词之间的空格、句首句尾的空字符串,都是经典坑点。二级的字符串题不会让你做KMP之类的高难度匹配,但“怎么把一个句子里的每个单词摘出来”这种基本功,是必练的。
第四,二维数组的角与边。二级大纲里二维数组只要求“初步认识”,但202503就在第四题考了矩阵外围一圈求和。这种题代码量不大,错起来特别隐蔽,四角会被重复计算,不画图很容易漏。
把这张地图记熟,再对照真题逐一击破,备考效率会高很多。
2. 高频真题逐题精解
2.1 数字拆分与条件计数:区间特殊数字统计
这道题是202503二级的第一道编程大题,也是绝大多数考生的送分题。题目大意是:给定两个整数L和R,统计区间[L, R]内有多少个正整数“不包含数字4且能被3整除”,输出满足条件的数字个数。
从考点上看,这道题同时覆盖了循环遍历、按位拆分数字、取模判断三个核心技能。拿到题的第一反应不是立刻写代码,而是把条件拆成两条:一是数字十进制表示里没有任何一位等于4,二是这个数整体能被3整除。
参考代码可以这样写:
#include <iostream> using namespace std; bool hasDigit4(int n) { while (n > 0) { if (n % 10 == 4) { return true; } n /= 10; } return false; } int main() { int L, R; cin >> L >> R; int cnt = 0; for (int i = L; i <= R; i++) { if (i % 3 == 0 && !hasDigit4(i)) { cnt++; } } cout << cnt << endl; return 0; }我特意把“判断一个数含不含4”这个逻辑封装成了函数hasDigit4,而不是全部写在main里面。这样做的原因有两点:一是代码可读性更高,main里面只剩循环和条件,阅卷人或者你自己回看代码时一眼就能懂;二是这个逻辑本身是独立的,抽成函数以后,如果后续题目改成“不含5且不能被7整除”之类的变体,直接改函数名和判断条件就行,不用动main里的结构。
按位拆数字的原理很简单:对10取余拿到个位,再除以10把个位挤掉。比如n=345,第一次n%10得到5,n/=10后n=34;第二次得到4,n=3;第三次得到3,n=0,循环结束。整个过程相当于把数字从右往左剥洋葱,每一层都是当前的最低位。这里需要注意一个细节:如果n本身就是0,while循环一次都不会执行,直接返回false。题目里L至少是1,但这个函数的通用性对边界情况要心中有数。
如果用一个更巧妙的写法,可以把数字转成字符串,然后用字符串查找来判断有没有字符’4’。这种写法在代码量上更短,但运行速度差不了多少,而且字符串处理的思路在第三题会用到。考试时用哪种都行,关键是稳定不犯错。我推荐基础阶段用取模拆位法,因为它不依赖额外的头文件和字符串API,思路直接贴合“数字”这一层语义。
这道题的隐藏考点是复杂度。L和R的范围题目没给到特别大,一般不超过100000,直接用for循环逐个判断,单次判断的耗时是数字位数级别,整体完全没问题。但如果有人自作聪明想用数学公式去算,反而容易算错,还浪费考场时间。
2.2 数组标记与去重输出:成绩统计
这题是202503二级的第二道编程题,考查数组的标记用法和排序思路。题目大意是:班上有n个同学参加模拟测试,每个同学有一个整数成绩(范围0到100),现在要把所有不同的成绩按从高到低输出,并且统计不同成绩的数量。
这题有两个关键点:第一是“去重”,第二是“从高到低排序”。C++里最直接的做法是定义一个大小为101的桶数组,用成绩作为下标,记录这个分数是否出现过。因为成绩的范围已经限制在0到100,桶数组的开销极小,这种思路既简单又不容易错。
参考代码:
#include <iostream> using namespace std; int main() { int n; cin >> n; bool appeared[105] = {false}; for (int i = 0; i < n; i++) { int score; cin >> score; appeared[score] = true; } int cnt = 0; for (int s = 100; s >= 0; s--) { if (appeared[s]) { cnt++; cout << s << " "; } } cout << endl << "cnt=" << cnt << endl; return 0; }用bool数组当“标记位”是这一题的灵魂。appeared[score] = true,意思就是“这个分数出现过”。全部读完后,从100往下扫描,遇见true就输出,天然就是降序,根本不用写排序算法。整个过程的时间复杂度是O(n+101),空间复杂度O(101),在考试环境下几乎可以认为不耗资源。
这个思路我建议所有考二级的人都练熟,因为它是后面学习桶排序、计数排序的前置铺垫。很多人一看到“排序”两个字,立刻想到冒泡或者选择排序,但在这个场景里,数据范围小且需要去重,桶的做法比排序算法更贴合题意。如果你对STL的sort和unique比较熟悉,也可以写:
#include <algorithm> int arr[105]; // 读入n个数到arr sort(arr, arr + n); int len = unique(arr, arr + n) - arr; for (int i = len - 1; i >= 0; i--) { cout << arr[i] << " "; }这种写法需要自己控制数组长度和unique的返回值,一旦搞混,容易输出多余的元素。如果对STL不够熟练,考场我更推荐用桶标记的方式,直观、不易错、代码也短。这道题还有一个容易忽略的输出细节——题目可能对输出格式有要求,比如最后一个数字后面有没有空格、输出顺序是先输出数量再输出列表还是反过来。读题时一定把输出格式圈出来,很多满分代码就栽在这个小地方。
2.3 字符串单词切分:句子反转
第三题是202503二级里字符串题的典型代表。题目大意是:输入一句英文句子,单词之间用若干个空格分隔,要求把每个单词里的字母顺序颠倒,但单词在句子中的顺序保持原样。输出处理后的句子。
这题考的其实是“字符串的边界切分”和“子串的逆序输出”。标点符号不参与处理,假设输入只包含大小写字母和空格。很多考生第一反应是:先把整个句子反转,再把每个单词反转回来——这是经典的双重反转法,适用于整个字符串逆序且单词内部顺序也要维持的场景,但这里要求单词内部颠倒、单词顺序不变,所以直接用一次“按单词逐字操作”更容易想清楚。
参考代码:
#include <iostream> #include <string> using namespace std; int main() { string line; getline(cin, line); string result = ""; int n = line.length(); for (int i = 0; i < n; ) { if (line[i] == ' ') { result += ' '; i++; continue; } string word = ""; while (i < n && line[i] != ' ') { word += line[i]; i++; } // 逆序输出这个单词 for (int j = word.length() - 1; j >= 0; j--) { result += word[j]; } } cout << result << endl; return 0; }这里有个很多人不知道的细节:cin >> s读入字符串时会跳过开头的空格,遇到空格就停下来,根本读不进包含空格的完整句子。所以句子输入必须用getline(cin, line)。string类的length()方法返回的是字符串长度,注意遍历时别把下标越界。
在写循环的时候,我是用“指针式扫描”的思路,维护一个下标i,从0往后走。遇到空格,说明单词之间的分隔符,直接在结果里补一个空格;遇到字母,就进入内层while循环,把连续的字母攒成一个word,然后逆序追加进result。等内层循环退出时,i要么走到了字符串末尾,要么停在了空格前,外层循环会继续处理。
这种写法可以应对多个连续空格的情况,因为每个空格都会单独执行一次result加空格,不会把多个空格吞成一个。有同学用cin逐词读入再反转每个词,遇到连续空格时输出格式就对不上,这就是丢分点。
从复杂度看,每个字符最多被处理两次(一次加进word,一次逆序输出),时间O(n),空间O(n)。n是整句长度,二级场景下完全没问题。做这类题时,我建议先在草稿纸上模拟一次,比如输入“I love GESP”,肉眼过一遍:I逆序还是I,love变evol,GESP变PSEG,输出“I evol PSEG”,再对照代码逻辑,基本不会错。
2.4 二维数组边界条件:矩阵外围求和
第四题是202503二级的压轴编程题,考查二维数组的遍历和边界去重。题目大意是:输入一个n行m列的整数矩阵,计算矩阵最外一圈所有元素的和并输出。
这题看起来简单,陷阱却不少。最直观的做法是写两重循环遍历所有元素,判断当前元素是否在边界上,是就累加。但判断“边界”条件本身就有讲究:行下标为0或n-1,或者列下标为0或m-1,这两个条件用或连接即可。参考代码:
#include <iostream> using namespace std; int main() { int n, m; cin >> n >> m; int a[105][105]; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> a[i][j]; } } int sum = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (i == 0 || i == n - 1 || j == 0 || j == m - 1) { sum += a[i][j]; } } } cout << sum << endl; return 0; }这种全遍历判断边界的方式,优点是逻辑简单,不容易漏元素;缺点是每个位置都判断了一次,但n和m通常不超过100,性能完全不是瓶颈。另一种写法是分四条边分别累加:先加第一行、最后一行,再加第一列、最后一列,但这样四个角落会重叠,必须在循环里跳过已经加过的位置,很多考生就错在重复计算四个角。我推荐的还是“遍历时判断边界”的统一处理思路,只写一个if,不做特判。
边界条件里还有一个坑:当n=1或m=1时,矩阵只有一行或一列,“外围”就等于整个矩阵,所有位置都满足边界条件,统一判断法依然成立。但如果是分四条边相加的写法,这时的去重逻辑就很难处理。所以考场遇到这种题,选择遍历判断法更稳。
这道题背后考查的是“用二维下标描述一个区域的位置关系”。你能把矩阵想象成格子图,行方向是纵轴、列方向是横轴,边界就是四条边和四个顶点。二级不要求你写DFS或BFS这类图论算法,但这种“把现实问题转成坐标判断”的模型,是后面算法学习的重要基础。
3. 易错点与避坑指南
3.1 历年考生最容易栽的几个坑
带学生复盘了多年二级真题,我总结出几个反复出现的错误模式。第一个就是**“循环边界差一”**。for (int i = 0; i < n; i++)里面,到底是i < n还是i <= n?遍历数组下标从0开始,最后一个元素的下标是n-1,所以必须写成i < n。如果用i <= n,就会访问a[n],越界。这个问题在数组题和矩阵题里出现频率极高。
第二个是整数除法与取模的混淆。比如判断一个三位数是不是水仙花数,需要拿到百位、十位、个位。百位是n / 100,十位是(n / 10) % 10,个位是n % 10。这几步里只要有一个取模或除法的位置写反,结果就完全错。不少考生把十位写成n % 100,得到的是后两位,不是十位。做题时先在草稿纸上写清每一位的表达式,再上机。
第三个是输出格式错误。GESP的判分系统对输出有严格的格式要求,末尾多一个空格有时候不算错,但要求换行的地方没有换行,有时候直接扣分。有些题明确要求“每个数之间用空格隔开”,你用cout << x << endl就会把后面的数换到下一行。考场最好把样例输出的格式一模一样的复刻一遍,特别是处理完最后一个数后不要多输出空格。
第四个是不写return 0。很多人觉得main函数结束自动返回,没有写return 0的习惯。在Xcode或VS Code的本地环境里可能不报错,但在评测系统里,某些编译器会给出警告甚至非零退出码,导致程序被判运行时错误。二级养成好习惯:main函数最后一定写return 0。
3.2 测试点设计与自测策略
很多考生平时做题只测样例数据,样例过了就提交,结果一上评测机就崩。原因是样例数据往往比较简单,根本覆盖不到边界情况。我建议每道编程题都至少准备五组自测数据:正常数据、最小值数据、最大值数据、重复数据、特殊形状数据。
拿矩阵外围求和题举例。样例一般给3行4列这种常规数据,你还要测n=1、m=1的情况,再测1行5列,再测5行1列。这些特殊形状下,边界判断条件是否仍然成立,只有测了才知道。再比如区间统计题,L=1、R=100000是最大范围,L=1、R=1是单元素区间,L=100、R=100是区间首尾相等,这些都必须测。
自测的方法是准备一个最小测试用例,然后用“人工推演一遍程序路径”的方式验证结果。比如输入“3 3”的矩阵:
1 2 3 4 5 6 7 8 9外围和是1+2+3+6+9+8+7+4=40。你跟踪代码的循环:i=0时,j=0,1,2全部累加;i=1时,j=0和j=2累加,j=1不满足边界条件不加;i=2时,j=0,1,2全部累加。最后结果40,和手算一致。这种用手动推演确认代码逻辑的过程,比直接提交然后反复修改试错要高效得多。
还有一个实用的自测技巧:在本地写代码时,用一组“极端数据”去跑,把中间过程用cout打印出来,比如统计题里把每个满足条件的数打印出来,看看跟你的判断是否一致。调通之后再把调试输出注释掉或删掉,提交干净的代码。这个习惯保持下来,最终上考场的时候犯错的概率会大大降低。
4. 考场实战与能力进阶
4.1 二级考场上拿到编程题后的操作顺序
我见过不少学生,拿到编程题就急着敲代码,也不读完整题目,结果敲到一半发现理解错了,又删掉重来,白白浪费半小时。正确的顺序应该是:读题三遍、划条件、定思路、写注释框架、再填充代码。第一遍快速浏览题目大意,第二遍把输入范围、输出格式、特殊要求划出来,第三遍在心中用人话复述一遍题目,确认没有歧义。
如果一道题读完三遍还没思路,不要硬抠。先跳过做下一道,把会做的稳定拿到分,再回头啃难题。二级的编程题一般是按难度排序的,但偶尔也有“第一题文字特别长”的情况,这时候你更要沉着。核心的得分策略是:四道编程题,争取前两道全对,第三道拿一大半,第四道写出暴力版本拿部分分。部分分在GESP判分里是有的,只要思路对、能处理部分数据,评测机就会给对应的分值,所以“不会做也要写一个能处理小数据的版本”,这一条非常重要。
上机的时候还要养成随时保存的习惯。虽然评测系统一般不会强制关闭,但考试紧张状态下误操作清空代码的情况不是没发生过。每写完一个函数就按一次Ctrl+S,写完一道题就整体提交一次,确保至少有一版能跑通的代码在系统里。
4.2 从二级到三级:下一步怎么练
二级通过以后,很多学生会问我下一步怎么衔接三级。GESP三级开始考递归、结构体、函数进阶和更复杂的一维数组应用,思维难度是跳跃式的。我建议在二级备考刚结束时,不要立刻刷三级真题,而是先用一周时间把二级里面不够熟练的点补扎实,特别是函数的封装和二维数组的操作。基本功稳了,三级的学习才会事半功倍。
三级最常出现的“递归”其实并非天外飞仙,它就是“函数调用自己”。学递归前最需要的是把函数调用的栈过程搞明白:调用时有哪些参数压栈,返回时怎么带回结果。你可以用一个简单的小例子——计算阶乘——在纸上手动模拟每一次调用和返回,画一个栈的进出图,这个图画明白了,递归就入门了。三级的结构体则是对“数据打包”的需求,可以把它理解成把多个相关变量捆在一起,相当于给数据建模,二级里的数组和它是一脉相承的。
对还在二级备考阶段的同学,我的建议是:每周至少完整写四道编程题,每道题都用“自主设计测试数据、手写推演、上机验证”的流程过一遍。编程不是一个靠“看懂”就能学会的技能,它像骑自行车,看再多视频不如自己蹬两圈。等你把202503这种难度的题目写熟到“看到题就能分出考点”的程度,二级证书基本就是囊中之物了。
我自己的体会是,GESP二级这个位置其实很妙——它不像一级那样只需要会输出Hello World,也不像三级那样要啃递归大山,而是刚好卡在“你会不会用语言解决问题”这个分界点上。这次202503的题再次印证了这一点:没有偏题怪题,但综合度比往年微调了,字符串题和矩阵题的分量在加重。备考时与其迷信押题,不如把每一类题的基本功练扎实。最后分享一个小技巧:每道题AC之后,花五分钟重新审视自己的代码,想想哪些地方可以抽成函数、哪些边界可以写得更严密。这个习惯,比多刷十道新题更有价值。