ARTICLE · 1029633
每周一套 GESP | C++ 三级真题精讲(2026年9月·第15次认证)
算法与证书之间 | 每周一套 GESP
GESP C++ 三级 真题精讲
2026 年 9 月 · 第 15 次认证
跟着真题学编程,稳步拿下考级
小朋友们好!这是 2026 年 9 月的 GESP C++ 三级真题,也是我们专栏第一次精讲三级卷子。三级和一、二级最大的不同是:这是一套几乎全在考"位运算"的卷子——二进制、原码反码补码、与或非、异或、移位、位掩码……选择题和判断题里一大半都和 0、1 有关。
别被"位运算"三个字吓到,它其实特别好玩:你可以把一个整数想象成一排开关,每个开关只有"开(1)"和"关(0)"两种状态,而 & | ^ ~ << >> 就是在批量拨这些开关。把这套开关想明白了,三级就拿下一大半了。
编程题两道:T1《公共二进制位》考按位与 + 统计 1 的个数,T2《分割字符串》考字符串处理,都是三级的经典套路,代码不长,跟紧思路一定能学会。
💡 学习建议:位运算题最好拿一张纸,把每个数写成二进制,一位一位对着算。解析里我会把关键的二进制都列出来,你跟着画一遍,比看十遍文字都管用!
& 按位与:两位都为 1 结果才是 1(像"而且")| 按位或:有一个为 1 结果就是 1(像"或者")^ 按位异或:两位不同为 1、相同为 0(不一样才通电)~ 按位取反:0 变 1、1 变 0(一键全翻转)<< 左移:所有位向左挪,右边补 0,挪一位≈乘 2>> 右移:所有位向右挪,正数挪一位≈除以 2 取整
优先级(高→低):~(取反) > << 、>>(移位) > &(与) > ^(异或) > |(或)。记不住没关系,不确定就加括号,计算机一定按括号算!
📝 单选题精讲(每题 2 分,共 30 分)
关于计算机的数据编码,下面说法不正确的是( )。
A. 数字 5,不管采用原码、反码、补码,它的 8 位二进制形式都是 0000 0101
B. 计算机科学中,单位换算默认 1 KB = 1024 B,而非 1000 B
C. 原码是最直观的一种有符号数表示方法
D. 反码运算中,最高位进位要抛掉 ✓
答案:D
📌 补码的进位"扔掉",反码的进位"加回最低位"。
关于进制转换,下面说法不正确的是( )。
A. 数字 1101B = 1×2³+1×2²+0×2¹+1×2⁰ = 8+4+0+1 = 13
B. 数字 42Q = 4×8¹+2×8⁰ = 32+2 = 34
C. 数字 2BEH = 2×16²+11(B)×16¹+14(E)×16⁰ = 512+176+14 = 702
D. 345Q = 0E3H(通常写作 E3H) ✓
答案:D
💡 同一数字可用多种进制表示,换算到十进制一比就知道等不等。
下面程序的运行结果是( )。
#include <iostream>
using namespace std;
int main() {
int x = 1;
for (int i = 0; i = 0; i <= 5) {
x++;
}
cout << x << endl;
return 0;
}
A. 1 ✓
B. 10
C. 15
D. 程序编译错误
答案:A
i = 0(赋值号 =),不是比较号 i == 0。它的执行过程是:先 int i = 0 初始化;然后求中间 i = 0,把 0 赋给 i,整个表达式的值就是 0,0 代表"假",循环条件一开始就不成立,循环体一次都不执行。所以 x 始终是初始化的 1,输出 1。⚠️ 程序没有语法错误,能编译能运行,只是循环不进;i++ 永远执行不到,也不会死循环。
📌 一个等号 = 是"赋值",两个等号 == 才是"判断是否相等",这是考试最爱挖的坑。
关于计算机编码中的补码,下面说法错误的是( )。
A. 正数的补码:与其原码、反码相同
B. 负数的补码:将其对应正数的原码按位取反(得到反码),然后加 1
C. 8 位的补码,最大表示数据是 128,最小表示数据是 -128 ✓
D. 负数的补码,从右往左扫描正数的二进制形式,遇到第一个 1 之后,左边的所有位都取反
答案:C
📌 n 位补码范围:-2 的 (n-1) 次方 ~ +2 的 (n-1) 次方 -1。8 位就是 -128~127。
下面程序执行的结果是( )。
#include <iostream>
using namespace std;
int main() {
int x = 2026;
int count = 0;
while (x > 0) {
count++;
x = x & (x - 1);
}
cout << count << endl;
}
A. 2026
B. 8 ✓
C. 16
D. 32
答案:B
x = x & (x - 1) 是位运算里最经典的一招,作用是:把 x 最右边的那个 1 抹掉。每执行一次就少一个 1,count 加一次,所以最终 count 就是 2026 的二进制里 1 的个数。2026 转二进制:2026 = 1024+512+256+128+64+32+8+2,写成二进制是 11111101010,数一数 1 一共有 8 个(我用编译器验证过,输出确实是 8)。📌 x & (x-1) = 消掉最右边的 1;循环到 0 的次数 = 二进制中 1 的个数。这个套路务必背下来!
关于位运算,下列说法错误的是( )。
A. 找唯一数:数组中唯一出现一次、其余出现两次的数,全部异或的结果即为该数(如 [5,7,9,7,5] 异或得 9)
B. 若左移导致高位溢出(超过整型范围),结果不再符合乘法规律
C. 有符号整数左移溢出时结果确定 ✓
D. n+2 位二进制数 A₁A₂…Aₙ00,一定是 4 的倍数(最后两位 00)
答案:C
💡 异或找唯一数是高频考点:成对抵消、孤家寡人留下来。
计算 3 & 7 | 15 ^ ~11(a=3, b=7, c=15, d=11)的结果是( )。
A. -9
B. -1
C. -7
D. -5 ✓
答案:D
~,再算 &,然后 ^,最后 |。① 3 & 7:0011 & 0111 = 0011 = 3;② ~11:11 = 0000 1011,取反后是 …1111 0100,即 -12;③ 15 ^ (-12):01111 异或 …10100 = …11011,即 -5;④ 3 | (-5):00011 或 …11011 = …11011 = -5。我用编译器实跑 3&7|15^~11,输出就是 -5,选 D。⚠️ 含 ~ 的位运算结果常是负数,别怕,负数在计算机里用补码表示,按位照算即可。
下面是一个加密解密的函数,横线处应该填入的程序段是( )。
#include <iostream>
#include <string>
using namespace std;
int main() {
int key; cin >> key;
string msg = "Hello GESP";
cout << msg << endl; // 原文
for (int i = 0; i < msg.size(); i++) {
msg[i] = msg[i] ^ key; // 加密
}
cout << msg << endl; // 密文
__________(横线处)__________
cout << msg << endl; // 又变回原文
return 0;
}
A. 循环执行 msg[i] = msg[i] | key;
B. 循环执行 msg[i] = msg[i] >> key;
C. 循环执行 msg[i] = msg[i] ^ key; ✓
D. 循环执行 msg[i] = msg[i] & key;
答案:C
msg[i] ^ key。异或有一个神奇的性质——同一个数异或两次会变回原样:(x ^ key) ^ key = x。所以解密只要把加密的动作原封不动再做一遍就行,选 C。而 | 、>> 、& 都不可逆,没法把密文还原成原文。🌰 异或加密是真实世界里最简单的加密:加密、解密用同一把钥匙、同一个操作。
🌰 灯泡比喻:按一下开关灯变状态(加密),再按同一个开关灯回到原来(解密)。
下列程序分别判断输入的 N 个数是否"可能"是二进制、八进制、十进制、十六进制,横线处应填入( )。
#include <iostream>
using namespace std;
int main() {
int n = 0; cin >> n;
for (int i = 0; i < n; i++) {
char str[11]; cin >> str;
char max = '0';
for (int i = 0; str[i] != '\0'; i++)
if (str[i] > max) max = str[i];
__________(横线处)__________
}
return 0;
}
A. cout << (max <= '1') << " " << (max <= '7') << " " << (max <= '9') << " " << (max <= 'F') << endl; ✓
B. 用数字 1、7、9、'F' 比较(前三个没加引号)
C. 用数字 1、7、9、F 比较(字符都没加引号)
D. 全部用 >= 反向比较
答案:A
max <=,且字符要带单引号,选 A。B、C 错在把字符 '1' 写成了数字 1——字符 '1' 的 ASCII 码是 49,和数字 1 完全不是一回事;F 不加引号更是语法错误。D 方向反了。⚠️ 字符常量必须加单引号。"字符 0"是 ASCII 48,不是数字 0。
位掩码管理权限:四个权限各占一个二进制位,mask 初始为 0。要依次完成:添加读、添加写、清除读(其他位不变)、添加删除,横线处应填( )。
const int READ = 1 << 0; // 读,第0位
const int WRITE = 1 << 1; // 写,第1位
const int MODIFY = 1 << 2; // 修改,第2位
const int DELETE_= 1 << 3; // 删除,第3位
int mask = 0; // 初始无任何权限
__________(横线处)__________
A. mask |= READ; mask |= WRITE; mask &= ~READ; mask |= DELETE_; ✓
B. mask &= READ; mask &= WRITE; mask |= ~READ; mask &= DELETE_;
C. mask |= READ; mask &= WRITE; mask &= ~READ; mask |= DELETE_;
D. mask |= READ; mask |= WRITE; mask |= ~READ; mask |= DELETE_;
答案:A
mask |= READ;添加写:mask |= WRITE;清除读且不动其他位:mask &= ~READ(先把读那一位取反成 0,再与一下,正好把这一位关掉);添加删除:mask |= DELETE_。完全对应 A。C 的第二步 mask &= WRITE 会把除写以外的位全清零;D 的 |= ~READ 会把几乎所有位都打开,都不对。📌 开灯用 |,关灯用 &= ~。这是位掩码的灵魂操作。
下面选项中提到的变量都是正整数,关于位运算,下面说法错误的是( )。
A. num >> 1 对于正整数,等价于对 num 做整数除以 2、向下取整
B. num | 0x0F 可以把 num 的低 4 位全部置为 1,高位保持不变
C. num ^ num 的结果一定等于 0
D. 若 num 右移时低位被移出丢弃,结果一定符合除法的数学规律 ✓
答案:D
已知 a=0, b=2, c=11, d=7, e=5,表达式 !a & b << c ^ d | e 的结果是( )。
A. 5
B. 13
C. 7 ✓
D. 28
答案:C
⚠️ !a 先变成 1,而 1 与 4096 相与是 0;这道题的坑全在优先级上,拿不准就加括号。
关于 string 的成员函数,下面说法错误的是( )。
A. size() 返回字符个数、不含末尾 '\0';length() 和 size() 完全等价
B. s.empty():字符串为空返回 true,否则返回 false
C. s.substr(pos, len) 省略第二个参数时,从 pos 一直截取到末尾
D. s.erase(2) 表示删除字符串中下标为 2 的这一个字符 ✓
答案:D
s.erase(2) 只传一个位置参数时,含义是从下标 2 开始,一直删除到字符串末尾,删掉的是"后面一整段",不是只删第 2 个字符。若只想删下标 2 这一个字符,要写 s.erase(2, 1)(第二个参数 1 表示删除长度为 1)。⚠️ erase(pos) 删到结尾,erase(pos, len) 才是删 len 个字符,务必分清。
以下数组定义,符合 C++ 语法的是( )。
A. int a[] = {1, 2, 3.0};
B. int b['A']; ✓
C. int c[ ];
D. float d[10.0];
答案:B
int b['A']; 中,'A' 是字符常量,它的 ASCII 码是整数 65,数组大小相当于 65,是一个合法的整型常量,所以 B 正确。A 错:int 数组里给了 3.0 浮点初值,类型不符;C 错:声明数组不给大小、也不初始化,编译器不知道开多大空间;D 错:数组大小必须是整数,10.0 是浮点数。📌 数组大小必须是"整型常量表达式",字符常量本质上也是个整数,所以可以。
1000 个酒缸恰好 1 缸有毒,毒酒喝下 24 小时后毒发;共有 48 小时,可做两轮实验,每轮能重新安排老鼠喝酒。每只老鼠对一缸酒有三种状态:两轮都不喝、只第一轮喝、只第二轮喝。要保证找出毒酒,最少需要几只老鼠?( )
A. 5
B. 7 ✓
C. 10
D. 9
答案:B
🌰 一只老鼠每轮只提供"喝/不喝",两轮组合出 3 种有效信号,本质是三进制编码。
💡 对比:只有一轮实验时每只老鼠只有 2 种状态,要用 2ⁿ ≥ 缸数(二进制);两轮就是 3ⁿ。
✅ 判断题精讲(每题 2 分,共 20 分)
补码做加法运算时,如果最高位产生进位,需要把该进位循环加到结果的最低位。
错误 ×
求 -13 的补码,可以对正数 13 的二进制,从右向左扫描,遇到第一个 1 之后,右边所有位全部取反、左边保持不变。
错误 ×
一个 8 位二进制原码表示的有符号数,可表示的数值范围是 -128 到 +127。
错误 ×
这段代码执行后,能够将数组中所有大于 0 的元素赋值为 0。
int a[] = {2, -5, 7, 0, -1};
int n = sizeof(a) / sizeof(a[0]);
for (int num : a) {
if (num > 0) {
num = 0;
}
}
错误 ×
for (int num : a):这里的 num 是数组元素的一份副本(拷贝),函数里把 num 改成 0,改的只是临时副本,原数组一个数都不会变。想真正修改数组,必须用引用:for (int &num : a)。所以说法错误。a 是 int 有符号整型,若 (a & 1) == 1 结果为 true,则 a 一定是正奇数。
错误 ×
a & 1 只看最低位。负数在计算机里用补码表示,负奇数的最低位同样是 1,例如 -1 的补码是全 1,-1 & 1 = 1,结果也为 true,可 -1 是负数。所以只能断定 a 是"奇数",不能断定"正",说法错误。char str[4] = "GESP"; 将在内存空间中给 str[4] 分配 5 个字节。
错误 ×
\0 实际需要 5 个字节,4 个格子装不下,char str[4] = "GESP" 是非法初始化,直接无法通过编译(编译器报错,而不是运行时越界)。"给 str[4] 分配 5 个字节"的说法自相矛盾、也是错的。正确写法应是 char str[5]。判断字符串中字符是否是 '0' 或者 '1',可以写条件:if(str[i]==0 || str[i]==1)。
错误 ×
'0'、'1' 的 ASCII 码分别是 48、49;而条件里写的 0、1 是整数。正确写法是 if(str[i]=='0' || str[i]=='1'),字符必须带单引号,错误。下面程序执行后,将输出 98。
#include <iostream>
using namespace std;
int main() {
int a = 'a', b = 'b';
if (a > 0 || b++ > 0) { }
cout << b;
}
正确 √
a > 0(97>0)已经为真,右边 b++ 就根本不会执行,b 保持 98 不自增。最后输出 b 仍是 98,说法正确(我用编译器实跑确认输出 98)。📌 || 左真则右不算,&& 左假则右不算——短路求值是判断题常客。
枚举可以在保证不漏解的前提下缩小枚举范围,不是必须遍历全部。
正确 √
错误 ×
💻 编程题精讲(每题 25 分,共 50 分)
题目描述:
小红有 n 个非负整数。把每个数写成二进制后,她想知道:有多少个二进制位,在所有数里都为 1。
二进制位从右向左编号为 0、1、2……;某个数若没有第 k 位(位数不够),就认为它的第 k 位是 0。
输入:第一行整数 n;第二行 n 个非负整数。
输出:一个整数,表示在所有数中都为 1 的二进制位数量。
样例:输入 3 个数 13、7、15,输出 2。
13 = 1101 7 = 011115 = 1111
竖着看每一位,第 0 位三个都是 1,第 2 位三个也都是 1,共 2 位,所以答案是 2。
数据范围:2 ≤ n ≤ 20000,0 ≤ aᵢ ≤ 10⁹。
题目要求"某一位在所有数里都为 1"。回忆 &(按位与)的规则:两位都为 1,结果才是 1。这不正好就是"所有数这一位都为 1"的意思吗?
所以分两步:第一步,把所有数全部按位与起来,得到 S。S 的某一位是 1,当且仅当每个数的这一位都是 1。第二步,数一数 S 的二进制里有几个 1,就是答案。数 1 可以用 S & 1 看最低位、再 S >>= 1 右移。
#include <iostream>
using namespace std;
int a[20010];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; ++i)
cin >> a[i];
int S = a[0]; // 先放第一个数
for (int i = 1; i < n; ++i)
S &= a[i]; // 依次把所有数"按位与"起来
int ans = 0;
while (S > 0) {
if (S & 1) ans++; // 最低位是1,计数+1
S >>= 1; // 右移一位,继续看下一位
}
cout << ans;
return 0;
}
⚠️ 易错点:1. S 的初值要设成第一个数 a[0],不能设成 0(设 0 的话,0 和谁与都是 0)。2. 位数不够的数高位自动算 0,与的时候天然满足题意,不用特殊处理。3. n 最大 2 万、数最大 10⁹,用 int 存得下,整体 O(n),时间足够。
题目描述:
小明按越来越"长"的连续空格来切字符串:
① 第一次:找到第 1 个空格,在这儿切开,丢掉这个空格;② 第二次:在剩下的右半段里,找到连续 2 个空格再切,丢掉这 2 个空格;③ 第三次:找连续 3 个空格……以此类推,每次需要的连续空格数加 1,直到再也找不到为止。
依次输出每次切下的左半段,最后再输出剩下的右半段。若一次都切不开,直接输出原字符串。注意:可能切出空字符串(空行也要照常输出,不能省)。
输入:一行字符串(只含大小写字母、数字、空格,且不以空格结尾)。
输出:若干行,依次为每次的左半段,最后一行是剩余的右半段。
样例 1:输入 sdfd 55 55 99312,输出四行:sdfd / 55 / 55 / 99312。
第一次用 1 个空格切下 sdfd;第二次需要连续 2 个空格,"55 55"中间只有 1 个不够,真正的连续空格在后面,切下 "55 55",右侧还多带 1 个空格;这时只剩 1 个空格,凑不齐第 3 次的 3 个,结束,最后输出 99312。
样例 2:输入 AbCd 999(中间 3 个空格),第一次切掉 1 个空格、左半段是 AbCd;剩下串以 2 个空格开头,第二次切出空字符串(要输出一个空行),最后剩 999。
数据范围:字符串长度不超过 1000,仅一行但含若干空格。
因为输入带空格,普通 cin 读到空格就停,所以必须用 getline(cin, s) 读整行。
用变量 k 表示"第几次分割",第 k 次就拼出 k 个连续空格 spaces,再用 s.find(spaces) 找它第一次出现的位置 pos:· 找不到(返回 -1)就 break;· 找到就输出 pos 左边的字符,然后把"pos+k 之后"的部分拼成新的 s,继续 k+1 次。循环结束后,把最后剩下的 s 输出即可(它天然处理了"一次都切不开"和"切出空行"两种情况)。
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "";
getline(cin, s); // 必须用getline读整行(含空格)
for (int k = 1; ; ++k) { // 第k次分割,需要连续k个空格
string spaces = "";
for (int i = 0; i < k; ++i)
spaces += " "; // 拼出k个连续空格
int pos = s.find(spaces); // 在当前串里找这k个空格
if (pos < 0 || pos >= (int)s.size())
break; // 找不到,就不能再分割了
for (int i = 0; i < pos; ++i)
cout << s[i]; // 输出分隔符左边
cout << endl;
string rest = "";
for (int i = pos + k; i < (int)s.length(); ++i)
rest += s[i]; // 跳过这k个空格,保留右边
s = rest; // 右边成为新的字符串继续分割
}
cout << s << endl; // 输出最后剩下的右侧字符串
return 0;
}
⚠️ 易错点:1. 一定用 getline 读入,cin 读不到空格。2. 每次找的是"连续 k 个空格",用 s.find(空格串) 最方便;切完要跳过这 k 个空格(从 pos+k 开始留右边)。3. 空字符串也要输出,循环里照常 cout 再 endl,就会留下一个空行。4. s.find 找不到返回 -1,这是结束循环的信号。
📊 本套考点分布:
• 二进制与编码:原码/反码/补码、范围、进位规则、进制换算(单选 1、2、4,判断 1、2、3)
• 位运算核心:& | ^ ~ << >>、优先级、x&(x-1) 数 1、异或找唯一数/加密、位掩码权限、右移除法(单选 5、6、7、8、10、11、12,判断 5)
• 字符与数组:字符常量与 ASCII、字符/数字区分、数组定义与越界、范围 for 引用(单选 9、14,判断 4、6、7)
• string 与程序阅读:size/empty/substr/erase、for 循环条件、短路求值、流程图(单选 3、13,判断 8、10)
• 数学思维:三进制编码找毒酒(单选 15)、枚举范围(判断 9)
• 编程题:按位与统计公共 1 位、按递增空格数分割字符串
💪 备考建议:
1. 把位运算当成"拨开关":六个运算符亲手在纸上算几十道,尤其练熟 ~、移位后的负数补码。
2. 背下三个高频套路:x&(x-1) 数 1、异或找唯一数/可逆加密、| 加权限 &~ 清权限。
3. 记牢运算符优先级:不确定就加括号,考试不会因为多加括号扣分。
4. 分清字符和数字:'0' 是 48、'A' 是 65,字符常量一定带单引号;字符串含空格用 getline。
5. 读程序题动手跟踪:for 三个表达式、短路 ||&&、范围 for 是否加引用,都要一步一步写下来。
6. 编程题先小后大:用样例在纸上模拟整个切分/与运算过程,再动手写,注意空行、初值和边界。
— END —
算法与证书之间 · 每周一套 GESP · 小昕
真题来源:CCF GESP 2026 年 9 月 C++ 三级认证