简介:面向CCF编程能力等级认证(GESP)1至8级考生的真题与考点资料包,覆盖Scratch图形化、Python、C++三条备考路径,适合需要按级别梳理考纲、练习历年试题并查漏补缺的中小学生、信息学奥赛入门者及指导教师。内容包含1至8级真题与答案、各等级认证提纲,以及客观题知识点整理;真题已更新至2024年9月并持续补充,客观题知识点目前更新至五级、第六级在筹备中,另附Python爬虫教程,便于在等级考试之外延伸项目实践。资源以zip压缩包交付,约339MB,文件总数与主要文件类型明细在页面暂无数据,具体目录需以解压后的实际内容为准。已有867人学习或下载,可用于分级备考、考前自测、题型归纳与知识点复盘,帮助读者按Scratch、Python、C++不同语言路线建立清晰的复习顺序。
1. 编程能力等级认证:先弄懂评分逻辑,再谈刷题量
编程能力等级认证的真题,翻车点常常不在算法,而在读题。带过几轮考级班的同学会发现一个反直觉现象:越是盯着“会不会写循环”练,越容易在输入格式、数据范围和输出换行上丢分。这门认证按级别递进,低级别考语法与流程控制,高级别才逐步引入数组、字符串、函数和简单算法,每一级都有明确的能力边界。想稳过,先把每一级的考点分布摸清,再用真题验证自己是不是真的会写,而不是只会看懂。下面按“考什么、怎么答、哪里错”的顺序拆,适合正在备考等级认证、准备信息学竞赛入门的孩子和陪练的家长。
2. 考点分布:分级考纲里真正拉开分差的四类题
等级认证的考纲条目看起来多,真正拉开分差的就是四类:输入输出与流程控制、数组与字符串、函数与递推、简单算法与复杂度。低级别丢分集中在格式和边界,高级别丢分集中在效率和下标。复习时间有限时,先把前两类的正确率压到接近满分,再攻后两类,性价比最高。
下面这张表不是官方分值,而是按公开样卷和历年考生反馈整理的复习权重,用来决定时间怎么分。
| 级别段 | 核心考点 | 建议复习占比 | 常见题型 |
|---|---|---|---|
| 入门级 | 输入输出、顺序、分支、循环 | 40% | 求和、判断、图形打印 |
| 进阶级 | 一维/二维数组、字符串、函数 | 35% | 统计、矩阵、回文 |
| 提高级 | 排序、查找、递推、简单算法 | 25% | 排序输出、二分、模拟 |
2.1 低级别:输入输出、分支与循环是送分区
低级别的题,本质是把自然语言翻译成几行代码。读两个数输出和、判断奇偶、累加求和、找最大值、按行打印星号,这些题型占了大头。它们不难,但送分区恰恰是失分重灾区,原因集中在三处:输入格式没看清、循环边界多一少一、输出少了换行。
练习时不要贪多,每天五道基础题限时完成,做完必须自己造三组数据验证:最小规模、最大规模、中间值。比如求和题,n=1 时程序会不会输出 0,n 取到题目上限时变量会不会溢出,这些都要亲手试。低级别把这三件事做成习惯,后面高级别才不会反复栽在同一个坑里。
循环边界可以记一个笨办法:把循环变量在纸上写出来。for (int i = 0; i < n; ++i)跑一遍 n=3,纸上写 0、1、2,确认正好三个数;换成i <= n就变成四个,一眼能看出来。这个动作花不了十秒,比考完再后悔划算。
2.2 中级别:数组与字符串决定能不能过线
到了中级别,题目开始要求“存下来再处理”。一维数组用来存一组数做统计、找最大最小、去重;二维数组处理矩阵转置、杨辉三角;字符串处理回文、字母统计、子串替换。这一段的分水岭是下标意识:数组下标从 0 开始,长度是元素个数,最后一个元素的下标是长度减一。
字符串题有两个高频失分点。第一,把字符和数字混在一起,'0'和0不是一回事;第二,大小写没统一,统计字母时只算了小写,大写全漏。练的时候刻意把这两类数据都造一遍:全大写、全小写、大小写混合、带空格。带空格的字符串要用整行读入,普通的cin >> s遇到空格就停,读进来只有半个句子。
数组和字符串的常用操作可以对照着记:
| 操作 | 数组写法 | 字符串写法 | 注意点 |
|---|---|---|---|
| 取长度 | 额外变量 n | s.size() | size 返回无符号数,比较时转 int |
| 访问元素 | a[i] | s[i] | 下标从 0 开始 |
| 遍历 | 计数循环 | 范围 for 或下标 | 范围 for 改值要加引用 |
| 判空 | n == 0 | s.empty() | 空串长度是 0,不是 1 |
2.3 高级别:函数、递推与排序是拉分项
高级别的题开始考“组织代码”的能力。函数用来封装重复逻辑,比如判断素数、求最大公约数、交换两个数;递推用来解决爬楼梯、斐波那契、路径计数这类有状态转移的题;排序和二分用来处理“按顺序输出”“查找第一个满足条件的位置”。
这一段最容易被忽视的是复杂度意识。n 只有 1000 时,双重循环完全跑得动;n 到了 100000,双重循环就是十的十次方,评测机直接超时。拿到题先看数据范围,再决定用哪种写法,这个顺序不能反。排序题也一样,数据量小可以手写冒泡,数据量大就要用标准库的排序函数,同时要会手写一遍,防止题目限制。
递推题的得分点在于把“第 i 项怎么由前面几项推出来”写清楚。爬楼梯是f[i] = f[i-1] + f[i-2],路径计数是dp[i][j] = dp[i-1][j] + dp[i][j-1],写代码之前先在纸上列前三项,验证递推式对不对,再落到循环里。这个习惯能挡掉一大半“样例过了但隐藏数据全错”的情况。
2.4 真题演练:三道分级样题的答题思路
第一道是低级别的多组数据求和并输出最大值。题干通常写“读入若干组数据,每组先给个数 n,再给 n 个整数,输出和与最大值”。很多同学只处理一组,或者最大值初值设成 0,遇到全负数就错。
#include <iostream> using namespace std; int main() { int n; // while (cin >> n) 表示一直读到没有数据为止,适配多组输入 while (cin >> n) { int sum = 0; int mx = -1000000000; // 初值要足够小,防止全负数时出错 for (int i = 0; i < n; ++i) { int x; cin >> x; sum += x; if (x > mx) mx = x; // 边读边更新,省掉一个数组 } cout << sum << " " << mx << endl; } return 0; }这段代码的关键在while (cin >> n),它把“还有没有下一组”交给输入流判断,不用提前知道组数。mx的初值取负的十亿,是因为题目数据范围通常在 int 内,取 0 会让全负数数据直接翻车。循环里没有开数组,边读边算,内存占用恒定,n 很大也不会爆。
第二道是中级别的字母统计。题干给一行字符串,要求按字母顺序输出每个出现过的字母和次数,大小写算同一个字母。思路是开一个 26 格的计数数组,把每个字符映射到 0 到 25 的下标。
#include <iostream> #include <string> using namespace std; int main() { string s; getline(cin, s); // 整行读入,保留空格 int cnt[26] = {0}; for (char c : s) { if (c >= 'a' && c <= 'z') cnt[c - 'a']++; else if (c >= 'A' && c <= 'Z') cnt[c - 'A']++; // 大写统一折到小写 } for (int i = 0; i < 26; ++i) { if (cnt[i] > 0) cout << char('a' + i) << " " << cnt[i] << endl; } return 0; }c - 'a'用的是字符的编码差值,把 a 到 z 压进 0 到 25。大写分支减的是'A',但存的位置和小写一致,所以统计结果合并。输出循环从 0 到 25,天然按字母序,不需要额外排序。这里用getline而不是cin >> s,就是为了不让空格截断输入。
第三道是提高级的冒泡排序。题干要求读入 n 个数,从小到大输出,n 不超过 1000。虽然标准库有排序函数,但考级常要求手写,评分点在看是否理解两层循环的范围。
#include <iostream> using namespace std; int main() { int n, a[1005]; cin >> n; for (int i = 0; i < n; ++i) cin >> a[i]; for (int i = 0; i < n - 1; ++i) { bool swapped = false; // 记录本轮有没有交换 for (int j = 0; j < n - 1 - i; ++j) { if (a[j] > a[j + 1]) { swap(a[j], a[j + 1]); swapped = true; } } if (!swapped) break; // 已经有序,提前收工 } for (int i = 0; i < n; ++i) cout << a[i] << " "; return 0; }外层跑 n-1 轮,每轮把当前最大的数送到末尾,所以内层比较范围是n - 1 - i,已经排好的尾巴不再碰。swapped是优化,某一轮一次交换都没有,说明整个数组有序,直接跳出。数组开 1005 是留余量,题目说 n 不超过 1000,多开几个防止下标写错时越界。
3. 高频考点:字符串、排序、模拟题的固定套路
翻历年真题,字符串、排序、模拟这三类出现的频率最高。它们的共同点是题干长、细节多、套路固定。把这三种套路练熟,考场上大部分题都能往模板上靠。这一章按题型拆,每类给一个可复用的骨架,再用真题验证。
3.1 字符串:回文、统计、替换三类模板
回文题的判断骨架是双指针:一个从前往后,一个从后往前,字符不同就判否,相遇就判是。这个骨架能直接搬到“判断回文数”“最长回文子串”的简化版。统计题的骨架是计数数组或哈希表,把字符映射成下标。替换题的骨架是遍历原串,遇到目标字符就拼接新内容,或者直接改原串。
回文判断的代码短,但边界多。空串算不算回文,单字符算不算,大小写要不要敏感,题目都会明确写。读题时把这几个条件圈出来,代码里对应加分支。下面是最基础的双指针版本:
bool isPalindrome(const string& s) { int l = 0, r = (int)s.size() - 1; // size 是无符号数,转 int 再减 while (l < r) { if (s[l] != s[r]) return false; ++l; --r; } return true; }l < r而不是l <= r,是因为中间那个字符不需要和自己比。(int)s.size() - 1先转有符号再减,避免空串时无符号数下溢成巨大值。函数参数用 const 引用,避免拷贝,这是字符串题的好习惯。
统计题的高频变形是“只统计字母”“忽略大小写”“按出现次数排序”。前两个在上一章的代码里已经处理,第三个需要在计数后把有值的字母拎出来排序。排序规则通常是次数降序、字母升序,写比较函数时两个条件都要写全,只写一个会在次数相同时顺序错乱。
替换题常考的是“把连续多个空格压成一个”。这类题不要用replace反复改串,边遍历边拼新串更稳。遇到空格就标记,下一个非空格字符到来时补一个空格,其余字符直接追加。这个写法一遍过,不会出现替换后下标错位的问题。
3.2 排序与查找:手写排序与二分的得分点
排序题的得分点不在“能排出来”,而在“排得对、排得稳”。冒泡、选择、插入三种手写排序要会至少两种,知道它们的循环范围怎么定。选择排序是每轮找最小值换到前面,插入排序是把当前元素插到已排好部分的正确位置,冒泡是相邻比较。三种写法都要能默出来,考试时根据题目限制选。
二分查找的高频考法是“在有序数组里找第一个大于等于目标的位置”,也就是常说的下界。手写时区间定义要统一,推荐左闭右开,循环条件是l < r,更新时l = mid + 1或r = mid,最后返回l。
int lowerBound(const int a[], int n, int target) { int l = 0, r = n; // 左闭右开,r 取 n 表示可以插入到末尾 while (l < r) { int mid = l + (r - l) / 2; // 防止 l + r 溢出 if (a[mid] < target) l = mid + 1; else r = mid; } return l; // 第一个 >= target 的下标,找不到时等于 n }mid写成l + (r - l) / 2而不是(l + r) / 2,是为了在 l 和 r 都很大时不溢出。返回值l有两个含义:找到了就是目标位置,没找到就是它应该插入的位置。这个性质在“统计小于某数的个数”这类题里直接能用。
排序和查找组合起来的高频题是“先去重排序,再二分查找”。去重可以在排序后用双指针完成,也可以直接用集合。考级环境里集合的常数比较大,数据量上万时手写去重更稳。去重后的数组长度变了,后续二分的右边界要跟着改,这是常见的失分点。
3.3 模拟题:把长题干翻译成状态表
模拟题是题干最长、最容易读懵的一类。它不考复杂算法,考的是把一段规则翻译成代码的能力。常见场景有排队叫号、计分排名、日期推算、电梯运行。这类题的破法是先画状态表:有哪几个变量,每个变量在每一步怎么变,变化的顺序是什么。
以“统计每个人答对题数并排名”为例,状态表至少有三列:人、答对题数、是否已输出。规则里通常藏着“答对题数相同按编号升序”这种细节,读题时用笔圈出来。变量定好再写循环,循环里按“读入一条记录、更新对应人的计数、标记状态”的顺序走,不要一边读一边输出,否则排序会乱。
日期题是模拟里的常客。核心是每个月的天数表和闰年判断。闰年规则写成一个函数,二月天数跟着变。跨年、跨月、月末加一天这些边界,造数据时逐个试。很多同学样例过不了,不是逻辑错,是月份数组下标从 0 开始还是从 1 开始没统一。
模拟题的时间分配要克制。题干读完先花一分钟写状态表,再花十分钟写代码,最后留五分钟造边界数据。上来就敲代码,写到一半发现变量少了一个,返工的时间够做两道基础题。
3.4 真题演练:两道高频题的完整答法
第一道是字符串统计的变形:给一行文本,输出出现次数最多的字母及其次数,次数相同输出字母序最小的。思路是计数数组加一次遍历比较。
#include <iostream> #include <string> using namespace std; int main() { string s; getline(cin, s); int cnt[26] = {0}; for (char c : s) { if (c >= 'a' && c <= 'z') cnt[c - 'a']++; else if (c >= 'A' && c <= 'Z') cnt[c - 'A']++; } int best = 0; for (int i = 1; i < 26; ++i) { // 只有严格大于才替换,保证次数相同时留下字母序更小的 if (cnt[i] > cnt[best]) best = i; } if (cnt[best] == 0) cout << "No letters" << endl; else cout << char('a' + best) << " " << cnt[best] << endl; return 0; }比较循环从 1 开始,best初始为 0,只有cnt[i]严格大于当前最优才更新,这样次数相同的字母不会被后面的覆盖,天然满足“字母序最小”。全无字母时输出提示,这个分支题干一般会写,读题时别漏。
第二道是模拟题:n 个人排队,每次报到 m 的人出队,输出出队顺序。这是约瑟夫环的简化版。用数组标记每个人是否还在队里,循环数数,数到 m 就输出并标记出队,直到所有人出完。
#include <iostream> using namespace std; int main() { int n, m; cin >> n >> m; bool out[1005] = {false}; // false 表示还在队里 int left = n; // 剩余人数 int pos = 0; // 当前报数位置 while (left > 0) { int step = 0; while (step < m) { if (!out[pos]) ++step; // 只有还在队里的人才报数 if (step == m) break; pos = (pos + 1) % n; // 环形后移 } cout << pos + 1 << " "; // 题目按 1 开始编号 out[pos] = true; --left; pos = (pos + 1) % n; // 从下一个人继续 } return 0; }外层控制剩余人数,内层数到 m。pos用取模实现环形移动,out数组标记出队的人,数数时跳过。输出pos + 1是因为题目从 1 编号,而数组从 0 开始。这段代码的易错点在“数到 m 之后从下一个人重新开始”,pos在出队后要再后移一次,漏了这一步会重复数到同一个人。
4. 真题精练:从读题到AC的完整链路
同一道题,有人二十分钟写完还检查了两遍,有人四十分钟还在改下标。差距不在打字速度,在流程。这一章把一道题的完整处理链路拆成四步:读题、实现、自测、分配时间。每一步都有固定动作,照着走能把低级失误压到最低。
4.1 读题:圈出输入输出、数据范围与关键词
读题的第一遍只做三件事:圈输入格式、圈输出格式、圈数据范围。输入是单组还是多组,每个数是什么类型,输出要不要换行、要不要空格分隔,这些决定代码骨架。数据范围决定变量类型和算法复杂度,n 到十万就不能写双重循环。
第二遍读题圈关键词。“至少”“恰好”“不超过”“连续”“不同”这几个词每个都对应一个分支。把“至少”看成“恰好”,答案直接错一半。题面上如果有样例,把样例在纸上手动跑一遍,确认自己理解的规则和样例输出一致,再动手写。
读题时还要注意隐藏条件。比如“字符串只包含小写字母”和“字符串可能包含空格”是两种完全不同的读入方式,前者用cin,后者必须getline。题目没明说但样例里有空格,按有空格处理。这个判断做错,后面代码写得再漂亮也是零分。
提示:读题时间不要省,一道题读三分钟,比写完发现理解错再返工省十分钟。
4.2 代码实现:以一道统计题为例
看一道综合题:给 n 个学生的成绩,输出平均分(保留两位小数)、最高分、以及高于平均分的人数。这道题把输入、数组、统计、格式化输出串在一起,是中级别的典型题。
#include <iostream> #include <iomanip> using namespace std; int main() { int n; cin >> n; int a[1005]; int sum = 0, mx = -1; for (int i = 0; i < n; ++i) { cin >> a[i]; sum += a[i]; if (a[i] > mx) mx = a[i]; } double avg = (double)sum / n; // 先转 double 再除,避免整数除法 int cnt = 0; for (int i = 0; i < n; ++i) { if (a[i] > avg) ++cnt; // 与平均分比较,注意 avg 是浮点数 } cout << fixed << setprecision(2) << avg << endl; cout << mx << endl; cout << cnt << endl; return 0; }(double)sum / n是关键,sum和n都是整数,直接除会截断小数。输出用fixed加setprecision(2)固定两位小数,这两个要一起用,只写setprecision可能输出成科学计数法。统计高于平均分的人数时,a[i]是整数、avg是浮点数,比较会自动提升类型,不需要手动转。
变量命名尽量见名知意,sum、mx、cnt、avg比a1、b2好排查。数组开 1005 是习惯性留余量,题目上限 1000。这些细节单独看都小,凑在一起就是“样例全过、隐藏数据挂掉”的根源。
4.3 自测与对拍:自己造数据的三个方向
代码写完,样例过了,不代表对。自己造数据至少覆盖三个方向:边界、特殊、随机。边界是最小规模和最大规模,n=1、n 取上限、所有数相同、所有数不同。特殊是题目里提到的极端情况,全负数、空串、只有空格、刚好等于平均分。随机是用脚本生成一批数据,和暴力解法对拍。
对拍的思路很简单:写一份肯定正确但很慢的暴力代码,再写一份要提交的代码,用同一组输入跑,比较输出。随机数据可以用几行脚本生成,跑一百组,有一组不同就停下来看那组数据。
# 生成 100 组随机数据并对拍,gen 生成数据,slow 是暴力解,fast 是提交解 for i in $(seq 1 100); do python3 gen.py > in.txt # 生成一组输入 ./slow < in.txt > out1.txt # 暴力解输出 ./fast < in.txt > out2.txt # 提交解输出 if ! diff -q out1.txt out2.txt > /dev/null; then echo "第 $i 组不一致" break fi doneseq 1 100控制组数,diff -q只判断是否相同不打印差异,break让第一组不一致就停下,方便定位。对拍不要求每次都做,但排序、二分、递推这三类题建议养成习惯,它们的错误往往藏在小数据里看不出来。
4.4 答题纸与时间分配:先拿稳的分
上机考试的时间分配按“先易后难”排。开考先花五分钟通读所有题,按把握程度标一二三档,一档题先做,做完立刻检查输入输出格式。二档题留足时间,三档题放最后,做不出来就把能拿的部分分写上,比如输出样例、写个暴力版本。
检查清单固定三条:输入方式对不对、输出格式对不对、数组够不够大。这三条能挡住大部分非逻辑失分。变量命名在考试里不用追求短,maxScore比mx多敲几个字符,排查时省的时间更多。代码写完不要急着交,用题目样例再跑一遍,手算一组边界数据再跑一遍,两遍都过再进入下一题。
5. 易错点:五类最常见的失分题
带练过程中反复出现的错误就那几类,每类都有固定的错法和固定的改法。这一章按“错在哪、为什么错、怎么答对”拆五条,都是考场上真实丢过分的地方。
5.1 输入输出类:格式、换行、多组数据
第一条:多组数据只处理了一组。题干写“输入包含多组数据,读到文件末尾”,代码里只写了一次cin >> n,后面的数据全没读。错在没识别多组输入的标志,这类题必须用while (cin >> n)或while (scanf(...) != EOF)包住整个处理逻辑。答对的办法是读题时看到“多组”“若干组”“直到输入结束”就条件反射写循环读入。
第二条:输出少了换行或多了空格。题目要求每个结果占一行,代码里用空格分隔;或者要求空格分隔,代码里每个数后面都跟一个空格,行末多出空格。多数评测机对行末空格宽容,但对换行严格。答对的办法是照着样例输出逐字符对,样例里是endl还是空格,代码里就一模一样。
5.2 边界与下标类:0、1、最大值、空串
第三条:最大值初值设成 0。题目数据全是负数,最大值应该是 -3,代码输出 0。错在初值没有覆盖数据范围,mx = 0只适合非负数据。答对的办法是初值取题目数据范围的下界再往下一点,比如-1e9,或者先读第一个数当初始最大值。
第四条:循环多跑一次或少跑一次。for (int i = 0; i <= n; ++i)访问了a[n],越界;或者i < n - 1漏了最后一个元素。错在边界没在纸上验。答对的办法是写完循环立刻用 n=3 在纸上跑一遍,写下 i 的每个取值,确认首尾都对。
5.3 类型与读题类:精度、溢出、“至少”与“恰好”
第五条:整数除法丢了小数,或者大数相乘溢出。求平均分写成sum / n,两个 int 相除直接截断;或者两个十万相乘,int 装不下变成负数。错在对类型和范围不敏感。答对的办法是除法前先转double,乘法前估算最大乘积,超过 int 范围就换long long。读题时把“至少”“恰好”“不超过”圈出来,每个词对应一个判断条件,写完逐条对照,别凭印象。
6. 答题技巧:上机前十分钟的检查清单
考场上真正拉开差距的,往往不是谁会更难的算法,而是谁少犯低级错误。我自己的习惯是开考前十分钟不碰代码,先把下面这张表在脑子里过一遍,再开始读题。
| 检查项 | 具体动作 | 对应失分 |
|---|---|---|
| 输入方式 | 单组还是多组,有没有空格 | 多组只读一组、字符串被截断 |
| 数据范围 | n 多大,要不要 long long | 溢出、超时 |
| 输出格式 | 换行、空格、小数位 | 格式错、精度错 |
| 边界 | n=1、全负数、空串 | 越界、初值错 |
| 时间分配 | 先易后难,留十分钟检查 | 会做的没做完 |
这张表不用背,做三套真题就能形成条件反射。真正要练的是“写完不等于做完”:每道题写完,先用样例跑,再手造一组边界数据跑,两遍都过才进入下一题。排序题重点看循环范围,字符串题重点看读入方式,统计题重点看初值和类型转换。
有一次带练,一个同学五道题样例全过,最后只拿了一半分。复盘发现三道题栽在输出格式,一道题最大值初值写成 0,还有一道多组数据只处理了第一组。这些错没有一个是“不会”,全是流程漏了。从那以后我每次让他交卷前强制走一遍检查清单,同样的分再没丢过。你也可以把这张表打印出来贴在桌前,练到不用看就能想起来为止。希望帮到你。
本文还有配套的精品资源,点击获取