2026 年 6 月 GESP C++ 四级真题 · 逐题详解(含题干与选项)
一、单选题(每题 2 分,共 30 分)
参考答案:B B C C B C B C C B C B C C A
第 1 题
小杨正在编写一个"数字交换器"程序,他希望通过函数交换两个变量的值。请问运行以下代码后,屏幕上输出的是( )。
voidexchange(int *a, int &b){int t = *a; *a = b; b = t;}intmain(){int x = 100, y = 200; exchange(&x, y);cout << x << " " << y;return0;}A. 100 200 B. 200 100 C. 200 200 D. 编译错误
答案:B
exchange 的第一个参数是指针 a(指向 x),第二个参数是引用 b(绑定 y)。执行过程:t = *a = 100;*a = b,即 x 被赋值为 200;b = t,即 y 被赋值为 100(因为 b 是 y 的引用,修改 b 就是修改 y)。所以最终 x=200,y=100,输出"200 100"。选 B。
考点:指针传参改变实参本体,引用传参同样直接改变被引用的变量,两者可以在同一函数里混用来实现"交换"。
第 2 题
下面程序想通过函数计算三门课总分,横线处应填入的是( )。
intsumScore(int a, int b, int c){return a + b + c;}intmain(){int chinese = 88, math = 95, english = 90;int total = __________;cout << total;return0;}A. sumScore B. sumScore(chinese, math, english) C. sumScore(int chinese, int math, int english) D. sumScore(a, b, c)
答案:B
函数调用只需要写函数名加实参列表,实参可以是已经定义好的变量(chinese、math、english),编译器会按顺序把它们传给形参 a、b、c。A 只写了函数名,没有调用(缺少括号和参数),得到的是函数指针而非调用结果;C 在调用时又写了类型(int chinese 等),这是定义形参的语法,调用时不能带类型;D 里的 a、b、c 在 main 函数中并未定义,属于未声明的变量。选 B。
第 3 题
下面程序输出结果是( )。
intaddOne(int x){return x + 1;}intmain(){int a = 6;cout << addOne(a) + addOne(3);return0;}A. 9 B. 10 C. 11 D. 12
答案:C
addOne(a) = addOne(6) = 7;addOne(3) = 4。两者相加 = 7 + 4 = 11。选 C。
考点:函数调用作为表达式的一部分参与运算,先分别求出每个函数调用的返回值,再做外层的加法。
第 4 题
关于下面程序,说法正确的是( )。
voidshow(){int stars = 5;}intmain(){cout << stars;return0;}A. 程序输出 5 B. 程序可以通过编译,但输出随机值 C. 程序不能通过编译,因为 stars 只在 show 函数中有效 D. 程序不能通过编译,因为 cout 不能输出变量
答案:C
stars 是在 show 函数内部定义的局部变量,其作用域仅限于 show 函数体,函数结束后该变量就被销毁、名字也不可见。main 函数中根本不存在名为 stars 的变量,直接引用会导致编译期报错"标识符未定义",而不是运行期输出随机值。选 C。
考点:局部变量的作用域边界,是变量生命周期与可见性区分的经典陷阱。
第 5 题
小杨在调试一个"等级提升"系统,代码逻辑如下,执行后 *p 的值是( )。
int lv = 5, next_lv = 6;int *p = &lv;*p = *p + 1;p = &next_lv;A. 5 B. 6 C. lv 的地址 D. next_lv 的地址
答案:B
先看前两步:p 指向 lv,*p = *p + 1 把 lv 从 5 改成 6(这一步只是"顺带"发生,题目问的是最终状态)。最后一句 p = &next_lv,让指针 p 重新指向 next_lv(值为 6)。所以此刻 *p 就是 next_lv 的值,即 6,而不是地址本身(地址是指针 p 的值,*p 解引用后取到的是数值)。选 B。
考点:指针可以在运行过程中重新指向不同变量,*p 取的始终是"当前所指对象的值"。
第 6 题
小杨正在开发一款名为"星际网格"的游戏,他用二维数组 int map[5][4]; 来表示地图。已知 int 占 4 字节,如果 map 的内存地址是 0x2000,则表达式 &map + 1 的地址值是( )。
A. 0x204c B. 0x205c C. 0x2050 D. 0x2058
答案:C
&map 取的是"整个二维数组"的地址,其类型是指向"5×4 的 int 二维数组"的指针,因此 &map + 1 按照"整个数组大小"为步长跳跃,而不是按单个 int 跳跃。整个数组大小 = 5×4×4字节 = 80字节 = 0x50。所以 &map + 1 = 0x2000 + 0x50 = 0x2050。选 C。
考点:&array 的指针类型是"指向整个数组"的指针,其加 1 的步长是整个数组的字节数,这一点常与"数组名(退化为首元素指针)+1 只跳一个元素"混淆。
第 7 题
执行完下面代码后,变量 val 的值是( )。
int data[] = {10, 20, 30, 40, 50};int *ptr = data + 2;int val = *(ptr - 1) + *(ptr + 1);A. 50 B. 60 C. 70 D. 80
答案:B
ptr = data + 2 指向 data[2]=30。ptr - 1 指向 data[1]=20,ptr + 1 指向 data[3]=40。val = 20 + 40 = 60。选 B。
考点:指针的加减运算按元素为单位偏移,不是按字节偏移;这题本质是"以 ptr 为中心访问左右相邻元素"。
第 8 题
某班 3 个小组、每组 4 名同学的分数存入下面的二维数组 score,则 score[1][2] 的值是( )。
int score[3][4] = { {80, 81, 82, 83}, {90, 91, 92, 93}, {70, 71, 72, 73}};A. 81 B. 90 C. 92 D. 72
答案:C
二维数组下标从 0 开始,score[1] 对应第 2 行 {90, 91, 92, 93},score[1][2] 是该行下标为 2 的元素,即 92。选 C。
考点:二维数组按"行、列"两级下标定位元素,注意下标与"第几个"之间要减一。
第 9 题
小杨定义了一个结构体 Hero 来表示游戏角色,下面哪种初始化方式会由于语法错误导致编译失败?( )。
structHero {string name;int hp;};A. Hero h = {"Arthur", 100};B. Hero h;h.name = "Arthur";h.hp = 100;C. Hero h = new Hero{"Arthur", 100};D. Hero *p = new Hero{"Arthur", 100};
答案:C
new Hero{...} 在堆上创建一个 Hero 对象并返回其地址,返回值类型是 Hero*(指针),而 C 选项左边的 h 却声明为 Hero(非指针类型),把一个指针直接赋值给非指针变量属于类型不匹配,无法通过编译。D 选项用 Hero *p 接收 new 返回的指针,类型匹配,是合法写法;A、B 都是结构体的标准初始化/逐字段赋值方式,均合法。选 C。
考点:new 表达式返回的是指针,接收变量的类型必须与之匹配。
第 10 题
下面程序输出结果是( )。
structBook {string title;int pages;};intmain(){ Book books[2] = {{"Math", 120}, {"Science", 150}};cout << books[1].title;return0;}A. Math B. Science C. 120 D. 150
答案:B
books[1] 对应第二个元素 {"Science", 150},books[1].title 就是 "Science"。选 B。
考点:结构体数组的初始化列表按顺序对应每个元素,访问时先用下标定位元素,再用点号访问其成员。
第 11 题
小杨在对"能量晶石"按亮度进行排序。如果两块晶石亮度相同,他希望保持它们在原始序列中的相对顺序。下列关于排序算法稳定性的说法,错误的是( )。
A. 冒泡排序是稳定的,因为只有在左边比右边大时才交换。 B. 插入排序是稳定的,因为它将元素插入到相等元素的右侧。 C. 选择排序是稳定的,因为它每次选出最小元素放在前面。 D. 稳定性是指排序后相等元素的相对位置不发生改变。
答案:C
A、B、D 都是关于稳定性的正确表述:冒泡排序仅在严格大于时交换,不会打乱相等元素的相对顺序;插入排序把新元素插入到与它相等的元素右侧,同样保序;D 是稳定性的标准定义。而选择排序的标准实现是"每轮找到最小值后,与当前起始位置的元素做一次交换",这个交换动作可能把一个元素从它原来的位置直接"跳"到很靠前的位置,跨过了与它相等的其他元素,从而破坏相对顺序——因此选择排序一般被认为是不稳定的,C 选项把这个结论说反了。选 C。
考点:稳定性不是由"每次选出最小值"这个操作本身决定的,而是由"交换/移动是否会跨过相等元素"决定的。
第 12 题
小杨的机器人正在能量踏板上跳跃,踏板编号为 1,2,3,…。跳到第 n 块踏板的方案数满足递推式 f(n)=f(n−1)+f(n−2)。若 f(1)=1, f(2)=2,则运行以下代码计算 jump(5) 的结果是( )。
intjump(int n){if (n <= 2)return n;int a = 1, b = 2, c = 0;for (int i = 3; i <= n; i++) { c = a + b; a = b; b = c; }return c;}A. 5 B. 8 C. 13 D. 21
答案:B
按递推式手算:f(1)=1, f(2)=2, f(3)=f(2)+f(1)=3, f(4)=f(3)+f(2)=5, f(5)=f(4)+f(3)=8。再核对代码:a=1,b=2 分别代表 f(1)、f(2);循环从 i=3 到 5,每轮 c=a+b(即当前的 f(i)),然后整体后移一位(a←b, b←c)。三轮迭代后 c 依次取得 f(3)=3、f(4)=5、f(5)=8,最终返回 c=8。选 B。
考点:用几个滚动变量代替数组实现递推(滚动数组/迭代法求 Fibonacci 类数列),是空间优化的常见写法。
第 13 题
在"模拟实验室"程序中,为了防止除以 0 导致崩溃,小杨使用了异常处理机制。执行以下代码将输出( )。
try {int x = 10, y = 0;if (y == 0) throw"Zero Error";cout << x / y;} catch (int e) {cout << "Error Code: " << e;} catch (constchar* msg) {cout << "Caught: " << msg;}A. 0 B. Error Code: 0 C. Caught: Zero Error D. 程序直接崩溃
答案:C
throw "Zero Error"; 抛出的是一个字符串字面量,其类型是 const char*,而不是 int,因此不会被 catch (int e) 捕获,而是精确匹配 catch (const char* msg) 这一分支,输出 "Caught: Zero Error"。由于异常在 x / y 执行之前就被抛出并处理,程序不会真正执行除以 0 的操作,也不会崩溃。选 C。
考点:C++ 的异常捕获按"抛出对象的实际类型"精确匹配对应的 catch 块,多个 catch 按顺序尝试类型匹配。
第 14 题
下面代码使用某种排序算法,将数组中的元素按从小到大排序。这段代码使用的排序算法是( )。
voidmystery_sort(double arr[], int n){for (int i = 0; i < n - 1; i++) {int minPos = i;for (int j = i + 1; j < n; j++) {if (arr[j] < arr[minPos]) { minPos = j; } }double temp = arr[i]; arr[i] = arr[minPos]; arr[minPos] = temp; }}A. 冒泡排序 B. 插入排序 C. 选择排序 D. 非典型排序
答案:C
外层循环固定当前处理位置 i,内层循环在 [i+1, n) 范围内扫描找出最小值所在下标 minPos,扫描结束后再把 arr[i] 与 arr[minPos] 做一次交换——这正是"每轮从未排序部分中选出最小元素放到已排序部分末尾"的选择排序标准写法,与冒泡排序(相邻元素比较交换)、插入排序(逐个插入有序区)的实现方式都不同。选 C。
第 15 题
小杨正在读取"冒险日志"文件 quest.txt。若文件内容为 Level 10,执行以下程序后输出为( )。
ifstream fin("quest.txt");string s;int v;fin >> s >> v;cout << s.length() * v;A. 50 B. 15 C. 70 D. 5
答案:A
用 >> 读取字符串时会以空白字符(空格/换行)为分隔符,因此 s 读到的是 "Level"(长度为 5),v 读到的是紧跟着的数字 10。s.length() * v = 5 * 10 = 50。选 A。
考点:ifstream 配合 >> 对文本文件按"空白分隔的词法单元"逐个读取,字符串和数字可以在同一行依次读出。
二、判断题(每题 2 分,共 20 分)
参考答案:✓ ✗ ✓ ✗ ✓ ✓ ✗ ✓ ✗ ✗
第 1 题
运行以下程序后,变量 a 的值最终会变为 20。
voidmodify(int *p){ *p = *p + 10;}intmain(){int a = 10; modify(&a);return0;}✓ modify 接收的是 a 的地址,函数体内通过 *p 直接操作 a 本身的存储单元,*p = *p + 10 把 a 从 10 修改为 20。指针传参可以在函数内部修改调用方的实参,表述正确。
第 2 题
在 C++ 中,引用一旦初始化并绑定到某个变量后,可以通过赋值语句将其重新绑定到另一个变量。
✗ 引用在初始化时完成绑定,之后这种绑定关系不可更改。后续对引用变量的任何赋值语句,实际操作的都是它一直绑定的那个原变量的值,而不会让引用"改指"到别的变量上(这一点与指针不同,指针可以随时通过赋值重新指向别的对象)。表述错误。
第 3 题
下面程序可以正确计算并输出 3 名学生的平均成绩。
structStudent {int id;int score;};intmain(){ Student students[3] = { {1, 90}, {2, 80}, {3, 100} };int sum = 0;for (int i = 0; i < 3; i++) { sum += students[i].score; }double average = sum / 3.0;cout << average << endl;return0;}✓ 循环正确累加了三名学生的 score(90+80+100=270),并用 sum / 3.0(浮点除法,避免整数除法截断)计算平均值,得到 90,程序逻辑与语法均无问题,能够正确计算并输出平均成绩。表述正确。
第 4 题
选择排序算法在寻找每一轮最小值时,如果遇到相等的元素不进行交换,则选择排序是一种稳定的排序算法。
✗ "内层查找最小值时遇到相等元素不交换"本身是选择排序内层循环的常规写法(只有找到更小的值才更新 minPos),并不是决定稳定性的关键。真正破坏稳定性的是外层"每轮结束后把最小元素换到当前位置"这个交换动作,它可能把一个元素跨过若干个与它相等的元素移动到前面,从而打乱原有的相对顺序。因此选择排序整体上仍然是不稳定的,题目的因果关系不成立。表述错误。
第 5 题
如果使用带 flag 的冒泡排序,且待排序数组一开始就是有序的,那么算法只需一轮扫描即可结束,时间复杂度为 O(n)。
✓ 带 flag(标记本轮是否发生过交换)的冒泡排序,在一轮扫描中如果没有发生任何交换,说明数组已经有序,可以立即提前结束。对于本来就有序的数组,第一轮扫描不会产生交换,flag 保持未置位,算法在跑完这一轮 O(n) 的比较后就直接退出,因此最好情况时间复杂度是 O(n)。表述正确。
第 6 题
在 C++ 中定义二维数组并初始化时,可以省略第一维,但不能省略第二维。因此 int a[][2] = {{1, 2}, {3, 4}}; 是合法的,而 int a[][] = {{1, 2}, {3, 4}}; 是不合法的。
✓ 二维数组在内存中是按"行"连续存放的,编译器只要知道每一行有多少个元素(第二维大小),就能根据初始化列表的总元素个数反推出行数(第一维),所以第一维可以省略;但如果第二维也省略,编译器无法确定每行的边界,属于非法语法。题目举出的两个例子分别符合"可以省略第一维"和"不能省略第二维"的规则,表述正确。
第 7 题
下面代码的时间复杂度是 O(2^n)。
int cnt = 0;for (int i = 1; i <= n; i++) {for (int j = 1; j <= i; j++) { cnt++; }}✗ 外层循环 i 从 1 到 n,内层循环执行 i 次,总执行次数为 1+2+…+n = n(n+1)/2,这是一个关于 n 的二次多项式,时间复杂度应为 O(n²),而不是指数级的 O(2^n)。表述错误。
第 8 题
假设文件 output.txt 能正常打开,下面代码通过 rdbuf 将 cout 的输出重定向到了文件中。
ofstream fout("output.txt");streambuf* old_buf = cout.rdbuf();cout.rdbuf(fout.rdbuf());cout << "GESP Exam";cout.rdbuf(old_buf);✓cout.rdbuf(fout.rdbuf()) 把 cout 底层使用的流缓冲区替换成 fout 对应的文件缓冲区,此后所有写往 cout 的内容实际上都被写入 output.txt 文件;之后再用 cout.rdbuf(old_buf) 把缓冲区还原回标准输出,使后续输出恢复正常。这是标准的"临时重定向 cout"技巧,表述正确。
第 9 题
小杨想通过下面程序给饭卡充值,程序会输出 70。
voidrecharge(int money){ money += 20;}intmain(){int card = 50; recharge(card);cout << card;return0;}✗ recharge 的参数 money 是按值传递,函数内部对 money 的修改只作用于函数体内的一份"拷贝",不会影响 main 函数中的 card。调用结束后 card 仍然是 50,输出应为 50 而不是 70。表述错误。
第 10 题
下面代码可以通过编译。
int a[5];a++;✗ 数组名 a 在多数场合会退化为指向首元素的指针,但它本身并不是一个可修改的左值(数组的地址在编译期就已固定,不能被重新赋值或自增),对数组名执行 ++ 操作会导致编译错误。表述错误。
三、编程题(每题 25 分,共 50 分)
3.1 编程题 1
试题名称:扫雷时间限制:1.0 s内存限制:512.0 MB
思路:这是一道典型的"网格标记 + 八邻域统计"模拟题。先把所有雷区在地图上标记为一个特殊值(比如 -1),再对每个非雷区区块,枚举其周围 8 个方向(用两层 -1~1 的偏移量 di、dj 组合,注意排除超出边界的情况),统计相邻雷区数量即可累加到该格子上。整体复杂度为 O(n·m·8),在数据范围(n,m ≤ 500)下完全可以接受。
#include<iostream>usingnamespacestd;int mp[510][510];intmain(){int n, m, q;cin >> n >> m >> q;for (int i = 0; i < q; ++i) {int x, y;cin >> x >> y; mp[x-1][y-1] = -1; // 标记雷区,注意题目行列号从1开始,需要减1转换成数组下标 }for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (mp[i][j] == -1) {cout << '*' << ' ';continue; // 雷区直接输出 *,跳过统计 }for (int di = -1; di <= 1; ++di) {for (int dj = -1; dj <= 1; ++dj) {if (i + di < 0 || i + di >= n || j + dj < 0 || j + dj >= m)continue; // 越界的邻居直接跳过if (mp[i+di][j+dj] == -1) mp[i][j] += 1; // 相邻格是雷区则计数加1 } }cout << mp[i][j] << ' '; }cout << '\n'; }return0;}考点:
输入的行列号从 1 开始,存入数组前要统一减 1 转换成下标; 用 -1 作为"雷区"的哨兵值,与后续计数区分开; 八邻域枚举时用双重循环生成 (di,dj) ∈ {-1,0,1}×{-1,0,1}(本题因为只在 mp[i][j]≠-1时才做统计,(di,dj)=(0,0) 时检查的是自身格,自身不是雷区所以不会误加,无需特殊排除);边界判断要在四个方向上都做越界检查,避免数组下标越界。
3.2 编程题 2
试题名称:身高体重指数时间限制:1.0 s内存限制:512.0 MB
思路:为每个小朋友维护一个包含编号、体重、身高的结构体,读入数据后按"身高体重指数从高到低"对结构体数组排序,最后依次输出排序后各元素的原始编号。由于数据范围 n ≤ 1000 较小,直接使用冒泡排序即可满足时间要求;排序时每次比较时临时计算两个人的 BMI(体重/身高的平方),不需要提前预处理成单独的数组,逻辑更直接。
#include<iostream>usingnamespacestd;structHuman {int id;int w;double h;} humans[1010];voidbubble_sort(int n){for (int i = 0; i < n; ++i) {for (int j = 1; j < n - i; ++j) {double bmi1 = (double)humans[j-1].w / humans[j-1].h / humans[j-1].h;double bmi2 = (double)humans[j].w / humans[j].h / humans[j].h;if (bmi1 < bmi2) swap(humans[j - 1], humans[j]); // BMI从高到低排序,前面小于后面就交换 } }}intmain(){int n;cin >> n;for (int i = 0; i < n; ++i) humans[i].id = i + 1; // 编号从1开始,先于读入体重身高就固定好for (int i = 0; i < n; ++i)cin >> humans[i].w;for (int i = 0; i < n; ++i)cin >> humans[i].h; bubble_sort(n);for (int i = 0; i < n; ++i)cout << humans[i].id << ' ';cout << endl;return0;}考点:
用结构体把"编号、体重、身高"绑定在一起,排序时整体移动,避免多个平行数组分别排序导致编号错位; BMI 计算中用 (double)强制类型转换,防止体重(int)与身高(double)混合运算或整数除法带来的精度问题;冒泡排序中比较条件是 bmi1 < bmi2时交换,实现的是"从高到低"排序,如果写反成bmi1 > bmi2就会变成从低到高,是本题最容易出错的细节。
四、知识点分类汇总:这套四级卷子各考点考了几道?
一句话看懂这张表:
四级相比三级,考点重心明显从"编码原理、位运算"转移到了"函数、指针与结构体"三大 C++ 核心机制——这三块合计 15 道题,占了单选+判断总数的一半以上,是四级区别于三级最典型的"能力台阶"; 排序算法从三级的"位运算优先级"式细节辨析,升级为对稳定性本质(是否跨过相等元素移动)的理解,说明四级要求考生不能只记结论,还要能推理"为什么稳定/不稳定"; 指针与引用的差异(能否重新绑定、按值/按址/按引用传参对实参的影响)反复出现在多道题中,是四级考察"函数与内存"关系的核心抓手; 编程题也从三级的"查表/字符处理"升级为"网格模拟+结构体排序",考察综合运用二维数组、结构体和基础排序算法解决略复杂问题的能力。
也就是说,四级想稳过,重心要放在函数与参数传递机制(值/指针/引用三种方式的区别)、指针与二维数组的地址运算、结构体与结构体数组的语法边界这三块。
如果想要更系统地刷 GESP 历年真题、看考点分布统计,或者做针对性专项训练,可以去www.gesppass.com 看看,站内整理了各级别的真题详解和知识点归纳,跟这份文档的风格是配套的,适合考前查漏补缺。
