2026 年 6 月 GESP C++ 四级真题 · 逐题详解

四季读书网 4 0
2026 年 6 月 GESP C++ 四级真题 · 逐题详解

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[] = {1020304050};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] = {    {80818283},    {90919293},    {70717273}};
  • 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 == 0throw"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] = {        {190},        {280},        {3100}    };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 转换成下标;
  2. 用 -1 作为"雷区"的哨兵值,与后续计数区分开;
  3. 八邻域枚举时用双重循环生成 (di,dj) ∈ {-1,0,1}×{-1,0,1}(本题因为只在 mp[i][j]≠-1 时才做统计,(di,dj)=(0,0) 时检查的是自身格,自身不是雷区所以不会误加,无需特殊排除);
  4. 边界判断要在四个方向上都做越界检查,避免数组下标越界。

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;}

考点

  1. 用结构体把"编号、体重、身高"绑定在一起,排序时整体移动,避免多个平行数组分别排序导致编号错位;
  2. BMI 计算中用 (double) 强制类型转换,防止体重(int)与身高(double)混合运算或整数除法带来的精度问题;
  3. 冒泡排序中比较条件是 bmi1 < bmi2 时交换,实现的是"从高到低"排序,如果写反成 bmi1 > bmi2 就会变成从低到高,是本题最容易出错的细节。

四、知识点分类汇总:这套四级卷子各考点考了几道?

知识点
涉及题目
题数
函数与参数传递(值/指针/引用)
单选1、单选2、单选3、单选4、判断1、判断9
6
指针与地址运算
单选5、单选6、单选7、判断10
4
数组与结构体(初始化/访问/结构体数组)
单选8、单选9、单选10、判断3、判断6
5
排序算法(稳定性辨析/算法识别)
单选11、单选14、判断4、判断5
4
递推与算法复杂度
单选12、判断7
2
异常处理(try-catch)
单选13
1
文件读写
单选15、判断8
2
引用语义
判断2
1
编程题(模拟统计/结构体排序应用)
编程1、编程2
2

一句话看懂这张表

  • 四级相比三级,考点重心明显从"编码原理、位运算"转移到了"函数、指针与结构体"三大 C++ 核心机制——这三块合计 15 道题,占了单选+判断总数的一半以上,是四级区别于三级最典型的"能力台阶";
  • 排序算法从三级的"位运算优先级"式细节辨析,升级为对稳定性本质(是否跨过相等元素移动)的理解,说明四级要求考生不能只记结论,还要能推理"为什么稳定/不稳定";
  • 指针与引用的差异(能否重新绑定、按值/按址/按引用传参对实参的影响)反复出现在多道题中,是四级考察"函数与内存"关系的核心抓手;
  • 编程题也从三级的"查表/字符处理"升级为"网格模拟+结构体排序",考察综合运用二维数组、结构体和基础排序算法解决略复杂问题的能力。

也就是说,四级想稳过,重心要放在函数与参数传递机制(值/指针/引用三种方式的区别)、指针与二维数组的地址运算结构体与结构体数组的语法边界这三块。

如果想要更系统地刷 GESP 历年真题、看考点分布统计,或者做针对性专项训练,可以去www.gesppass.com 看看,站内整理了各级别的真题详解和知识点归纳,跟这份文档的风格是配套的,适合考前查漏补缺。

2026 年 6 月 GESP C++ 四级真题 · 逐题详解-第1张图片-四季读书网

抱歉,评论功能暂时关闭!