简介:2023年9月GESP C++一级真题为参加CCF编程能力等级考试的考生提供了贴近真实考场的练习材料,尤其适合C++入门学习者、信奥赛备赛选手及需要检验语法基础的开发人员。文档完整收录选择题、判断题与两道编程题,并配套核心知识点解析,覆盖内存与存储设备、变量命名规则、逻辑与算术运算、短路求值、复合赋值、逗号表达式以及ASCII字符运算等易错内容,同时给出因数求解、数字平方和等典型算法思路,可帮助读者系统梳理一级考纲。资源包内共1个docx文件,大小147KB,排版清晰,便于打印自测或对照解析逐题复盘。目前已有902人浏览学习,参考价值较高。借助真题实测,读者不仅能熟悉GESP一级的题型难度,还能发现自身薄弱环节并针对性补强,为后续二级、三级进阶奠定扎实基础。
1. 一份真题卷里的 C++ 一级考点全貌
2023 年 9 月 GESP C++ 一级真题里,最值得反复琢磨的不是某一道难题,而是一组看起来互相矛盾的判断题:C++ 表达式 ('1' + '1') 的值是 '2' 吗? cout 算不算 C++ 关键字? continue 无条件执行会不会让 for 陷入死循环?三个问题摆在一起,最容易暴露出“背过语法但没真正理解求值过程”的问题。这套卷子的价值也正在于此:单选 30 分覆盖变量命名、运算符优先级、关系与逻辑表达式、循环边界这些 C++ 一级核心知识点,判断 20 分专挑初学者容易想当然的细节下手,最后两道 25 分的编程题要求把读入、运算、分支、循环完整串起来。本文按考点主线、题目陷阱、编程题实现、验证技巧四个部分展开,代码全部可以直接运行复现。
2. 考点主线:从变量命名到运算符优先级的六个必会点
2.1 内存分类与变量命名规则
选择题前两题属于“一眼题”,但恰恰是 GESP 一级最稳定的送分点。第一题问“内存”属于计算机中的哪类设备,答案是存储设备。这里有个容易混淆的点:寄存器、Cache、内存、外存都属于存储体系,但考试语境下的“内存”特指主存,即 RAM 这类可随机读写的存储设备,而不是输入、输出或打印设备。第二题考变量命名, red star 不能作为变量名,因为 C++ 标识符不允许出现空格。变量名只能由字母、数字、下划线组成,且不能以数字开头,不能与关键字冲突。
命名规则在后续写代码时影响很大。 redStar 、 RedStar 、 red_star 都是合法命名,分别对应驼峰、大驼峰和下划线风格,GESP 一级一般不强制风格,但要求能判断合法性。判断标识符合法性时,先看首字符,再看剩余字符,最后查关键字表,三步走完不会出错。
2.2 算术、逻辑、赋值运算符混用时的求值顺序
表达式 2 - 1 && 2 % 10 的求值结果是 1,而不是 0 或 2。原因在于 C++ 运算符优先级:算术运算符( - 、 % )高于关系运算符,关系运算符高于逻辑运算符,所以先算 2 - 1 = 1 、 2 % 10 = 2 ,再算 1 && 2 。C++ 中逻辑运算的结果是布尔值, true 输出为 1,因此答案是 B。这个考点还延伸出两个常见变体:一是把 && 换成 || 看短路特性,二是把算术表达式换成赋值表达式看右结合性。
#include <iostream>
using namespace std;
int main() {
// 算术优先级高于逻辑:先算 1 和 2,再算 1 && 2
cout << (2 - 1 && 2 % 10) << endl; // 1
// 逗号表达式:从左到右执行,返回最后一个表达式的值
int a = 1, b = 2, c = 3;
int val = (b = 3, c = 4, b + c); // b=3, c=4, 返回 7
cout << val << endl; // 7
// 复合赋值与赋值混合:先算右侧赋值,再做复合赋值
int d = 10;
d -= d = 6 / 2; // 先 d=3,再 d=d-3=0
cout << d << endl; // 0
return 0;
}
这段代码演示了三个很容易写错的地方。 (b = 3, c = 4, b + c) 是逗号表达式,整个表达式的值是最后一个子表达式的值,即 7,而不是 3 或 4。 d -= d = 6 / 2 涉及赋值运算符的右结合性,先执行 d = 3 ,再执行 d -= 3 ,结果为 0。这类复合赋值在笔试中反复出现,实际开发时不建议这样写,但考试必须能读出来。
2.3 关系表达式与短路求值的判定
选择题第 5 题给定了 int x = 2, y = 4, z = 6 ,四个选项分别考察 > 、 != 、 || 、 ! 混合使用。最值得展开的是 D 选项 x < y || !x < z 。很多初学者会把 !x < z 理解成 !(x < z) ,逻辑完全不同。 ! 是一元运算符,优先级高于 < ,所以 !x < z 等价于 (!x) < z 。当 x 为 2 时, !x 为 0, 0 < 6 为真。再加上 x < y 已经是真, || 左侧为真则整个表达式为真,右侧甚至不需要计算,这就是短路求值。
短路求值在实际代码中最常见的应用是保护表达式: if (p != NULL && p->value > 0) ,左侧为假时右侧不会执行,避免空指针访问。GESP 一级不考指针,但考短路逻辑的判断,理解“左侧能决定结果时右侧不执行”这条规则,选择题和判断题都能快速得分。
2.4 for 循环边界与步长的三种写法
判断题第 5 题的 for (int i = 0; i < 10; i += 2) 表示 i 从 0 开始,到 10 结束但不包含 10,间隔为 2,所以实际取值是 0、2、4、6、8,共 5 次。这个考点在选择题第 13 题的因数遍历中再次出现:输入 10 要输出 1、2、5、10,循环必须包含 n 本身。四个选项里 i < n + 1; i++ 是正确写法,因为 i <= n 与 i < n + 1 在整数范围内等价。
#include <iostream>
using namespace std;
int main() {
int n = 10;
// 遍历 1 到 n,包含 n
for (int i = 1; i < n + 1; i++) {
if (n % i == 0) {
cout << i << " ";
}
}
return 0;
}
这里要特别注意 i + 1 和 i++ 的区别。 i + 1 只是计算一个新值,没有写回 i ,循环会一直停在初始值附近; i++ 才真正改变循环变量。选择题第 13 题的 B 选项 int i = 1; i < n + 1; i + 1 就是典型的“写的像递增、实际是死循环”陷阱。
2.5 do-while 与 while 的死循环边界
判断题第 7 题说 do-while 循环不可能导致死循环,这是错的。do-while 的特点是“先执行循环体,再判断条件”,因此至少执行一次,但只要循环体内不改变条件相关变量,它同样会死循环。比如 do { cout << 1; } while (true); 就是死循环。第 8 题的 continue 陷阱更值得展开:很多人认为 continue 无条件执行就会跳过一切导致死循环,但在 for 循环里, continue 跳过的只是循环体中它之后的语句, for 括号里的迭代表达式 i++ 依然会在每次循环末尾执行。
#include <iostream>
using namespace std;
int main() {
int count = 0;
for (int i = 0; i < 10; i++) {
continue; // 跳过循环体剩余部分
count++; // 永远不会执行
}
cout << count << endl; // 0
return 0;
}
对比一下:如果把这个改成 while 循环且没有在 continue 前更新 i ,那才是死循环。 for 的迭代表达式由循环机制保证执行, while 的循环变量更新写在循环体里则可能被 continue 跳过。这个区别是 C++ 一级的高频考点,也是实际写业务逻辑时常见的 bug 来源。
2.6 输出格式与 cout 的输出陷阱
选择题第 4 题考察输出格式。如果代码是 cout << "a+b=" << a + b; ,且 a=3, b=4 ,输出就是 a+b=7 ,没有多余空格。题目选项里 3+4= 7 和 3+4=7 的差别只在空格,这种细节在 GESP 一级里经常出现,需要区分字符串字面量中的空格和运算符两侧的空格。判断题第 4 题 cout << (2, 3, "23") 的输出不是 2,3,23 ,而是 23 ,因为逗号表达式取最后一个子表达式的值作为整个表达式的值。
| 代码片段 | 实际输出 | 原因 |
|---|---|---|
cout << (2 - 1 && 2 % 10); | 1 | 算术先算,两个非零值逻辑与为真 |
cout << (2, 3, "23"); | 23 | 逗号表达式返回最后一个值 |
cout << ('1' + '1'); | 98 | 字符按 ASCII 码相加,49+49=98 |
cout << int(3.14); | 3 | 浮点转整型直接截断小数 |
这张表基本覆盖了 GESP 一级选择题和判断题里所有“输出是什么”的套路。字符参与算术运算时自动提升为 int ,这是后面第 3 章要专门讲的陷阱。
3. 真题里的语法陷阱:选择题与判断题逐题拆解
3.1 选择题第 6 题:b = c % 2.5 为什么是语法错误
题目问四个语句哪个不符合 C++ 语法。A 选项 c += 5; 是复合赋值,合法。C 选项 a = (b = 3, c = 4, b + c); 是逗号表达式嵌套赋值,合法。D 选项 a -= a = (b = 6) / (c = 2); 虽然可读性差,但语法合法,按右结合性先算 a = 3 再算 a -= 3 ,结果 a 为 0。B 选项 b = c % 2.5; 才是语法错误: % 是取余运算符,要求两个操作数都是整型, 2.5 是 double 类型,不能直接参与取余运算。
这个知识点在真实开发中的对应场景是:想对一个浮点数取余时,需要先用 int 转换或使用 <cmath> 中的 fmod 函数。GESP 一级目前只考核整型取余,但理解“ % 的操作数必须是整数”这个限制,能避免在后续学习浮点数运算时踩坑。值得顺手记住的是, int(3.14) 的结果是 3,这是判断题第 3 题的考点,浮点转整型采用的是截断而不是四舍五入。
3.2 选择题第 5 题:四个选项逐一判定真值
给定变量 x=2, y=4, z=6 ,四个选项的真值推导过程如下。A 选项 x > y || x > z : 2 > 4 为假, 2 > 6 为假,假或假为假。B 选项 x != z - y :先算 z - y = 2 , 2 != 2 为假。C 选项 z > y + x :先算 y + x = 6 , 6 > 6 为假。D 选项 x < y || !x < z : 2 < 4 为真,短路求值直接得到真。
| 选项 | 表达式 | 推导过程 | 结果 |
|---|---|---|---|
| A | x > y || x > z | 假 || 假 | 假 |
| B | x != z - y | 2 != 2 | 假 |
| C | z > y + x | 6 > 6 | 假 |
| D | x < y || !x < z | 真 || 任意,短路 | 真 |
这类题的关键是严格按照优先级分层计算:先括号、再算术、再关系、再逻辑、最后赋值。初学者容易先入为主地把 !x < z 读成 !(x < z) ,一旦读错,整个选项的判定就跟着错。
3.3 选择题第 14 题:各位数字平方和的提取顺序
题目要求输出正整数 1234 各位数字的平方和,即 1*1 + 2*2 + 3*3 + 4*4 = 30 。横线处需要填的是“取当前个位数字并平方”的代码。正确答案是 D 选项 (n % 10) * (n % 10) 。C 选项 n % 10 只取了数字没有平方;A 选项 n / 10 取的是去掉个位后的部分;B 选项 (n / 10) * (n / 10) 平方的是商而不是个位。
#include <iostream>
using namespace std;
int main() {
int n = 1234;
int sum = 0;
while (n > 0) {
int digit = n % 10; // 取个位
sum += digit * digit; // 累加平方
n /= 10; // 去掉个位
}
cout << sum << endl; // 30
return 0;
}
这个算法的核心是“取个位、累加、去个位”三步循环。 n % 10 负责取当前最低位, n / 10 负责把数字右移一位,整个过程直到 n 变为 0 结束。GESP 一级对数字拆分的考核基本就是这个套路,后续二级会把它包装成回文数、水仙花数等变体,原理完全相同。
3.4 判断题高频陷阱:字符运算、continue、cout 标识符
判断题第 6 题问 ('1' + '1') 的值是否为 '2' 。答案是错误的。C++ 中字符字面量参与算术运算时,会按照 ASCII 码值提升为整型, '1' 的 ASCII 码是 49, 49 + 49 = 98 ,输出结果为整数 98,而不是字符 '2' 。如果确实想得到数字 2,应该写成 ('1' - '0') + ('1' - '0') ,利用字符 '0' 到 '9' 的 ASCII 码连续这一性质完成字符到数字的转换。这个技巧在后续处理数字字符串时非常常见,一级考的是概念,二级就开始要求实际使用了。
判断题第 8 题的错误在于把 continue 的作用范围理解错了。 continue 跳过的是循环体中它之后的语句,不会跳过 for 循环的迭代表达式。真实开发中, continue 通常配合条件使用: for 遍历数组时跳过某个特定值,然后继续处理下一个元素。无条件 continue 虽然不常见,但并不会导致 for 死循环。判断题第 9 题问 cout 是否能作为变量名,答案是“可以”。 cout 是标准库中预定义的对象名,不是 C++ 关键字,所以语法上允许定义名为 cout 的变量,但这样做会遮蔽标准输出对象,导致 cout << 无法正常工作,属于“合法但极其不推荐”的写法。GESP 一级不考指针,这个判断题本质上是在考察关键字与标识符的边界。
3.5 判断题答案汇总与失分点
判断题答案可以整体汇总如下,方便考前快速核对。
| 题号 | 考察点 | 答案 |
|---|---|---|
| 1 | 早期计算机字库扩展卡使用 ROM | T |
| 2 | 神威·太湖之光超级计算机 | T |
| 3 | int(3.14) 的结果 | T |
| 4 | cout << (2, 3, "23") 的输出 | F |
| 5 | for 循环的步长与边界 | T |
| 6 | ('1' + '1') 的值为 '2' | F |
| 7 | do-while 不会死循环 | F |
| 8 | 无条件 continue 导致死循环 | F |
| 9 | cout 是 C++ 关键字 | F |
| 10 | C++ 是高级程序设计语言 | T |
可以看出判断题的 F 选项集中在两类错误:一是对运算符语义的理解偏差(第 4、6 题),二是对循环控制机制的简化想象(第 7、8 题)。第 9 题单独考关键字与对象名的区别,属于概念辨析。第 1、2 题是计算机常识题,答案均为正确,第 10 题考语言分类也是常识。考前把所有判断题的 F 选项重新推导一遍,比单纯背答案更能提高正确率。
4. 编程题全解:买文具与幸运数的完整实现
4.1 买文具:四路输入与输出分支
编程题第一题“买文具”是标准的“读入四个整数、计算总价、分支输出”题型。签字笔 2 元一支,需要 X 支;记事本 5 元一本,需要 Y 本;直尺 3 元一把,需要 Z 把;手里有 Q 元。输入约定为四行,每行一个正整数,X、Y、Z 的范围都是 1 到 10。输出要求两行:第一行是 Yes 或 No ,第二行是剩余钱数或缺少的钱数。
| 输入行 | 变量 | 含义 | 约束 |
|---|---|---|---|
| 第 1 行 | X | 签字笔数量 | 1 ≤ X ≤ 10 |
| 第 2 行 | Y | 记事本数量 | 1 ≤ Y ≤ 10 |
| 第 3 行 | Z | 直尺数量 | 1 ≤ Z ≤ 10 |
| 第 4 行 | Q | 手里的钱 | 正整数 |
#include <iostream>
using namespace std;
int main() {
int x, y, z, q;
cin >> x >> y >> z >> q;
int cost = 2 * x + 5 * y + 3 * z; // 总价:单价乘以数量累加
if (q >= cost) {
cout << "Yes" << endl;
cout << q - cost << endl; // 剩余钱数
} else {
cout << "No" << endl;
cout << cost - q << endl; // 缺少的钱数
}
return 0;
}
代码的逻辑非常直接:先把三个文具的费用累加成 cost ,再和 Q 比较。 Q >= cost 时够买,输出 Yes 和剩余金额;否则输出 No 和差额。注意 Q - cost 和 cost - Q 正好互为相反数,分支条件决定了哪边减哪边,不会出现负数输出。原题样例输入在原卷中为空,这里给出一组自测数据: X=2, Y=1, Z=1, Q=20 时,总价 4+5+3=12 ,输出 Yes 和 8 ;把 Q 改为 10 ,输出 No 和 2 。
4.2 小明的幸运数:个位判断与倍数判断的组合
第二题“小明的幸运数”比第一题多了一层条件组合。题目定义:所有个位数为 k 的正整数,以及所有 k 的倍数,都是 k 幸运数。要求计算 L 到 R 之间(包含两端)所有幸运数的和。第一组样例中 k=7, L=1, R=10 ,只有 7 一个幸运数,输出 7;第二组 k=7, L=10, R=20 ,14 是 7 的倍数,17 的个位是 7,输出 31。
#include <iostream>
using namespace std;
int main() {
int k, L, R;
cin >> k >> L >> R;
long long sum = 0; // 累加和,防止极端情况下 int 溢出
for (int i = L; i <= R; i++) {
if (i % 10 == k || i % k == 0) { // 个位是 k,或是 k 的倍数
sum += i;
}
}
cout << sum << endl;
return 0;
}
这里最关键的逻辑是 || 而不是 && 。 i % 10 == k 判断个位, i % k == 0 判断倍数,两个条件满足任意一个就累加。样例中 7 既是 7 的倍数、个位又是 7,但条件判断是或关系,所以只会累加一次,不会重复计数。求和变量使用 long long 是一个好习惯,虽然本题约束 R <= 1000 用 int 足够,但后续遇到 R 增大到一万、十万时, int 仍然够用,到千万级别就可能溢出,提前用 long long 可以省去以后排查溢出的麻烦。
4.3 编程题两个高频扣分点
编程题的扣分点往往不在算法而在输出格式和边界条件。第一处是输出行数, Yes / No 之后必须换行再输出金额,不能写成一行两个值。很多考生用 cout << "Yes " << q - cost; 一次性输出,看起来结果差不多,但评测系统按行比对,直接判错。第二处是循环边界,“包括 L 和 R”意味着循环条件必须是 i <= R ,写成 i < R 会漏掉最右侧的数值。另外, k 的倍数这一条包含 k 本身,如果 L = 1 ,循环到 i = k 时 i % k == 0 成立,不会漏加。
5. 从真题到考场:代码验证与提速技巧
5.1 用一个验证程序还原选择题结论
选择题和判断题的结论,与其靠背诵,不如写一个程序一次性验证。下面这段代码把前文涉及的争议表达式全部打印出来,在 Dev-C++ 或配置好 C/C++ 插件的 VS Code 里直接运行,五秒钟就能看到结果。
#include <iostream>
using namespace std;
int main() {
cout << (2 - 1 && 2 % 10) << endl; // 1
cout << ('1' + '1') << endl; // 98
cout << (2, 3, "23") << endl; // 23
cout << int(3.14) << endl; // 3
int x = 2, y = 4, z = 6;
cout << (x < y || !x < z) << endl; // 1
cout << (x != z - y) << endl; // 0
return 0;
}
跑完并对照输出后,再把判断题第 7、8 题的两种循环写法各试一遍,区分 for 与 while 在 continue 行为上的差异。这种“用程序验证语法结论”的方式比反复看解析有效得多,因为输出结果会直接纠正你的错误预期。VS Code 配置 C/C++ 环境时注意装好 C/C++ 扩展和编译器,Dev-C++ 则开箱即用,更适合考前突击。
5.2 提交前的自查清单
| 检查项 | 验证方法 | 典型症状 |
|---|---|---|
| 输出行数 | 对比题目输出描述逐行核对 | 第一行正确、第二行缺失 |
| 循环边界 | 用 L=R 的极端样例测试 | 边界值漏加 |
| 运算优先级 | 复杂表达式中显式加括号 | 结果与手算不一致 |
| 变量类型 | 累加和大数场景改用 long long | 大输入出现负数 |
| 样例复现 | 输入原题样例比对输出 | 与样例不一致 |
检查顺序建议先跑样例,再构造极端数据。买文具题可以构造 Q = cost 的恰好相等场景,确认输出 Yes 和 0 ;幸运数题可以构造 L = R = 1 的场景,手工算一遍再对照程序输出。这一步能把边界错误挡在提交之前。
5.3 三行模板:读入、计算、输出分离
GESP 一级编程题统一采用“读入三个数、循环计算、输出一个结果”的结构。把主函数拆成三个清晰的段落,可以避免代码揉在一起后找不到 bug。
#include <iostream>
using namespace std;
int main() {
// 第一步:读入
int k, L, R;
cin >> k >> L >> R;
// 第二步:计算
int sum = 0;
for (int i = L; i <= R; i++) {
if (i % 10 == k || i % k == 0) {
sum += i;
}
}
// 第三步:输出
cout << sum << endl;
return 0;
}
读入部分保持与输入描述完全一致的变量顺序,计算部分只做一件事,输出部分单独成段。这套模板的另一个好处是:当题目从 L、R 扩展到更大的数据范围时,只需要改第二步的循环逻辑,比如把枚举换成等差数列求和公式,读入和输出完全不动。真题卷里这两道编程题恰好一个考分支、一个考循环,把上面的模板练熟,一级的编程题部分就有保底了。



被折叠的 条评论
为什么被折叠?



