夜雨聆风学习资料网

ARTICLE · 1086409

GESP 2026年9月 C++ 四级真题,答案与知识点解析

GESP 2026年9月 C++ 四级真题,答案与知识点解析

一、单选题(每题 2 分,共 30 分)

第 1 题

小杨使用指针修改计数器的值。执行下面程序后,输出结果是(  )。

C++

int count = 8;

int *p = &count;

*p += 4;

cout << count << " " << *p;

return 0;

A. 8 8

B. 8 12

C. 12 12

D. 12 8

答案:C

知识点解析

本题考查指针的基本用法。

int *p = &count 使 p 存放 count 的地址,*p += 4 就是对 count 本身加 4,count 由 8 变为 12。由于 *p 与 count 是同一块内存,cout << count << " " << *p 输出 12 12,选 C。D 选项误以为 *p 与 count 是两份独立数据,这是对“指针存地址、解引用访问原变量”理解不到位。

第 2 题

关于下面指针声明的说法,正确的是(  )。

C++

int a = 10;

int b = 20;

const int *p = &a;

A. 可以通过 p 修改 a 的值

B. 可以令 p = &b

C. p 的指向和所指向的值都不能修改

D. p 必须始终指向 a

答案:B

知识点解析

本题考查 const 修饰指针的语义。

const int *p 是“指向常量的指针”:不能通过 *p 修改所指对象的值(如 *p = 20 会编译报错),但指针变量 p 本身可以改变指向,因此可以令 p = &b,选 B。A 错在不能通过 p 修改 a 的值;C 错在指向可以修改;D 与 B 矛盾。若要求“指向不能改”,应写成 int * const p。

第 3 题

小杨用二维数组记录仓库货物数量。执行下面代码后,变量 x 的值是(  )。

C++

int goods[3][4] = {{2, 4, 6, 8}, {10, 12, 14, 16}, {18, 20, 22, 24}};

int (*p)[4] = goods;

int x = *(*(p + 1) + 2);

A. 12

B. 14

C. 20

D. 22

答案:B

知识点解析

本题考查二维数组与行指针。

int (*p)[4] = goods 定义 p 为指向“含 4 个 int 的一维数组”的指针,p 指向第 0 行,p + 1 指向第 1 行;*(p + 1) 得到第 1 行数组名(即 goods[1]),再 + 2 得 &goods[1][2],解引用后为 goods[1][2] = 14,选 B。一般规律:*(*(p + i) + j) 等价于 goods[i][j]。

第 4 题

下面函数用于将一个 3 行 5 列二维数组的第 r 行元素全部加 1,横线处正确的形参写法是(  )。

C++

void addOne(________, int r) {

for (int j = 0; j < 5; j++) {

arr[r][j]++;

}

}

A. int arr[][]

B. int **arr

C. int arr[][5]

D. int arr[5][]

答案:C

知识点解析

本题考查二维数组作函数形参的写法。

二维数组传参时可以省略第一维(行数),但第二维(列数)必须写明,因为编译器要靠列数计算每一行的地址偏移量,因此 int arr[][5] 正确,选 C。A 缺少列数,编译报错;B 的 int** 是“指针的指针”,与静态二维数组类型不兼容;D 把行列位置写反,列数缺失、第一维还不匹配。

第 5 题

执行下面程序后,输出结果是(  )。

C++

int score = 60;

void update(int &score) {

score += 5;

}

int main() {

int score = 80;

update(score);

cout << score << " " << ::score;

return 0;

}

A. 85 60

B. 80 65

C. 85 65

D. 80 60

答案:A

知识点解析

本题考查引用传参、变量作用域与 :: 作用域运算符。

update 的形参是 int &score(引用),绑定到 main 中的局部变量 score,执行后 80 + 5 = 85,故第一个输出 85;::score 显式访问全局变量 score(初值 60),它从未被修改,故第二个输出 60。注意:局部变量与全局变量同名时,函数内直接写名字访问的是局部变量,加 :: 才是全局变量。故输出 85 60,选 A。

第 6 题

执行下面程序后,输出结果是(  )。

C++

struct Device {

int id;

int state;

};

void reset(Device d) {

d.state = 0;

}

void start(Device &d) {

d.state += 1;

}

int main() {

Device d{7, 2};

reset(d);

start(d);

cout << d.id << " " << d.state;

return 0;

}

A. 7 0

B. 7 1

C. 7 2

D. 7 3

答案:D

知识点解析

本题考查结构体传参方式:按值传递与按引用传递的区别。

reset(Device d) 按值传递,形参 d 是原对象的副本,d.state = 0 只改副本,不影响 main 中的 d;start(Device &d) 按引用传递,直接修改原对象,state 由 2 变为 3。id 从未被修改仍为 7,故输出 7 3,选 D。若希望 reset 也生效,应改为 void reset(Device &d)。

第 7 题

小杨定义了结构体数组,并使用指针访问其中的元素。执行下面代码后输出的是(  )。

C++

struct Book {

string name;

int pages;

};

int main() {

Book books[2] = {{"C++", 120}, {"Math", 150}};

Book *p = books + 1;

p->pages += 10;

cout << books[1].name << " " << books[1].pages;

return 0;

}

A. C++ 120

B. Math 150

C. Math 160

D. C++ 160

答案:C

知识点解析

本题考查结构体数组、指针算术与 -> 运算符。

books + 1 指向 books[1](结构体指针加 1 按元素大小移动),p->pages 等价于 (*p).pages 即 books[1].pages,执行 p->pages += 10 后由 150 变为 160。p 与 books[1] 是同一个对象,因此输出 Math 160,选 C。

第 8 题

关于冒泡排序、插入排序和选择排序,下列说法正确的是(  )。

A. 三种排序算法的最坏时间复杂度都是 $O(n)$

B. 冒泡排序只能从小到大排序,不能从大到小排序

C. 插入排序每次将一个待排序元素插入前面已经有序的序列中

D. 选择排序每轮只需要比较一次就能确定最小元素

答案:C

知识点解析

本题考查冒泡、插入、选择三种排序的思想与复杂度。

C 正确:插入排序每一轮把一个待排序元素插入到前面已经有序的序列中的合适位置。A 错误:三种排序的最坏时间复杂度都是 O(n²) 而不是 O(n);B 错误:把比较条件反向即可实现从大到小排序;D 错误:选择排序每轮需要逐个比较(约 n−i 次)才能确定最小元素,不是只比较一次。故选 C。

第 9 题

某机器人每次可以向前移动 1 格或 2 格,到达第 n 格的方法数由下面函数计算。ways(6) 的返回值是(  )。

C++

int ways(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 b;

}

A. 8

B. 10

C. 13

D. 21

答案:C

知识点解析

本题考查递推(斐波那契型)问题的迭代实现。

走到第 n 格的方法数满足 ways(n) = ways(n−1) + ways(n−2)(最后一步要么跨 1 格、要么跨 2 格),程序用 a、b 两个变量滚动递推:i=3 时 c = 1+2 = 3,i=4 时 c = 2+3 = 5,i=5 时 c = 3+5 = 8,i=6 时 c = 5+8 = 13,返回 b = 13,选 C。相比直接递归,这种迭代写法时间复杂度 O(n),避免了重复子问题的重复计算。

第 10 题

对一组 struct student 的学生按成绩(score)升序排序。排序前后的数据如下。关于该排序的稳定性,判断正确的是(  )。

C++

struct student {

int score;

char id;

};

排序前:

文本

(90, 'A'), (80, 'B'), (90, 'C'), (80, 'D')

排序后:

文本

(80, 'B'), (80, 'D'), (90, 'C'), (90, 'A')

A. 稳定,因为所有成绩已经按升序排列

B. 稳定,因为分数相同不会影响排序结果

C. 不稳定,因为相同成绩的 (90, 'A') 和 (90, 'C') 的相对顺序发生了改变

D. 无法判断,因为没有给出排序算法的代码

答案:C

知识点解析

本题考查排序稳定性的判定方法。

稳定性指关键字相等的元素在排序后保持原有的相对顺序。排序前 (90, ‘A’) 在 (90, ‘C’) 之前,排序后却变成 (90, ‘C’) 在前,相等元素的相对顺序被破坏,因此该排序不稳定,选 C。A、B 只看分数本身是否有序,没有关注相等元素的次序;判断稳定性只需要比较排序前后的数据,并不需要知道排序算法的代码,D 错误。

第 11 题

下面代码使用插入排序将数组按升序排列,横线处应填写(  )。

C++

void insertionSort(int a[], int n) {

for (int i = 1; i < n; i++) {

int key = a[i];

int j = i - 1;

while (j >= 0 && __________) {

a[j + 1] = a[j];

j--;

}

a[j + 1] = key;

}

}

A. a[j] < key

B. a[j] > key

C. a[j] == key

D. a[j + 1] > key

答案:B

知识点解析

本题考查插入排序的实现细节。

升序插入排序中,key 是本轮待插入的元素,只要它前面的 a[j] 比 key 大,就把 a[j] 后移一位腾出位置;当 a[j] ≤ key 时停止,把 key 放到 a[j + 1],故填 a[j] > key,选 B。A(a[j] < key)会在遇到更小元素时继续后移,排出的不是升序;C(a[j] == key)只有相等才移动,一般元素都不动,无法完成排序;D 比较的是空位 a[j + 1] 而不是前面的元素,逻辑错误。

第 12 题

下面代码的时间复杂度是(  )。

C++

int countPairs(int a[], int n) {

int cnt = 0;

for (int i = 0; i < n; i++) {

for (int j = i + 1; j < n; j++) {

if (a[i] + a[j] == 100) {

cnt++;

}

}

}

return cnt;

}

A. $O(1)$

B. $O(n^3)$

C. $O(n)$

D. $O(n^2)$

答案:D

知识点解析

本题考查双重循环时间复杂度的分析。

外层循环执行 n 次;内层 j 从 i + 1 执行到 n − 1,总执行次数约为 (n−1) + (n−2) + … + 1 = n(n−1)/2,是 n 的二次多项式,故时间复杂度为 O(n²),选 D。记忆方法:两层相互独立的嵌套循环,复杂度按次数相乘;单层线性循环才是 O(n)。

第 13 题

假设文件 data.txt 的内容如下:

文本

Blue Sky

执行下面程序后,输出结果是(  )。

C++

int main() {

ifstream fin("data.txt");

string a, b;

fin >> a >> b;

cout << b << "-" << a;

fin.close();

return 0;

}

A. Blue-Sky

B. Sky-Blue

C. Blue Sky

D. Sky Blue

答案:B

知识点解析

本题考查文件输入流 ifstream 与 >> 的读取规则。

fin >> a >> b 以空白(空格、换行、制表符)为分隔符依次读取两个单词,a 得到 “Blue”,b 得到 “Sky”。输出语句先输出 b、再输出 ‘-’、最后输出 a,因此结果是 Sky-Blue,选 B。注意 >> 会自动跳过前导空白,且读取顺序(先 a 后 b)与输出顺序(先 b 后 a)相反。

第 14 题

执行下面程序后,输出结果是(  )。

C++

int main() {

try {

int age = -1;

if (age < 0)

throw age;

cout << "A";

} catch (const char *msg) {

cout << "B";

} catch (int value) {

cout << "C" << value;

}

return 0;

}

A. A

B. B

C. C-1

D. 程序崩溃

答案:C

知识点解析

本题考查 C++ 异常处理 try / catch / throw 机制。

age = −1 满足 age < 0,throw age 抛出 int 类型的异常,其后的 cout << "A" 被跳过;异常按 catch 书写顺序进行类型匹配,catch (const char *msg) 不匹配 int,catch (int value) 匹配成功,输出 “C” 再输出 value 的值 −1,即 C-1,选 C。注意异常按类型精确匹配:字符串字面量 “Error” 的类型是 const char*,不会匹配 catch(int)。

第 15 题

下面函数使用冒泡排序将数组按升序排列。为了在数组已经有序时提前结束,两处横线应分别填写(  )。

C++

void bubbleSort(int a[], int n) {

for (int i = n - 1; i > 0; i--) {

bool changed = __________;

for (int j = 0; j < i; j++) {

if (a[j] > a[j + 1]) {

int t = a[j];

a[j] = a[j + 1];

a[j + 1] = t;

changed = __________;

}

}

if (!changed)

break;

}

}

A. false,true

B. true,false

C. false,false

D. true,true

答案:A

知识点解析

本题考查冒泡排序的“提前结束”优化。

changed 用来记录本轮是否发生过交换:每轮开始前置为 false(假设已经有序),一旦发生交换就置为 true;一轮结束后若 changed 仍为 false,说明本轮没有任何交换,数组已经有序,执行 break 提前结束排序。因此两处分别填 false 和 true,选 A。经过该优化,对已经有序的数组只需一趟扫描,时间复杂度可降为 O(n)。

二、判断题(每题 2 分,共 20 分)

第 1 题

执行下面程序后,变量 a 的值为 15。

C++

int a = 10;

int *p = &a;

*p += 5;

答案:√

知识点解析

本题考查通过指针间接修改变量。

p 中存放的是 a 的地址,*p 就是 a 本身,因此 *p += 5 等价于 a += 5,10 + 5 = 15。故说法正确。

第 2 题

一个函数必须在调用之前既声明又定义。

答案:×

知识点解析

本题考查函数声明与定义的关系。

C++ 只要求在调用点之前能够看到函数的声明(原型),完整的定义可以放在调用之后,甚至放在其他源文件中由链接器解析。因此“必须在调用之前既声明又定义”的说法过于绝对,错误。

第 3 题

下面二维数组在内存中按行优先连续存储,因此 *(*(a + 1) + 0) 的值为 5。

C++

int a[2][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};

答案:√

知识点解析

本题考查二维数组的行优先存储与指针运算。

二维数组在内存中按行优先连续存放,a + 1 指向第 1 行,*(a + 1) 即 a[1](第 1 行数组名),*(a + 1) + 0 即 &a[1][0],再解引用得 a[1][0] = 5。故说法正确。

第 4 题

执行下面程序后会输出 20。

C++

void change(int x) {

x = 20;

}

int main() {

int x = 10;

change(x);

cout << x;

return 0;

}

答案:×

知识点解析

本题考查函数按值传递。

change 的形参 int x 是实参的副本,在函数内修改副本不影响 main 中的 x,程序输出的仍是 10 而不是 20。若想真正修改实参,应传引用(void change(int &x))或传指针。故说法错误。

第 5 题

下面结构体初始化语句是合法的。

C++

struct Point {

int x;

int y;

};

Point p{3, 4};

答案:√

知识点解析

本题考查结构体的聚合初始化。

Point p{3, 4} 按成员声明的顺序依次初始化 x = 3、y = 4,是 C++ 支持的合法写法(等价于 Point p = {3, 4})。故说法正确。

第 6 题

对于按升序实现的稳定插入排序,移动元素的条件通常应为 a[j] >= key,这样能够保证相等元素的相对顺序不变。

C++

while (j >= 0 && a[j] >= key) {

a[j + 1] = a[j];

j--;

}

答案:×

知识点解析

本题考查插入排序稳定性的实现条件。

稳定的升序插入排序应使用严格大于 a[j] > key:遇到相等元素就停止移动,key 插在相等元素的后面,保持它们原有的相对顺序。若使用 a[j] >= key,相等的元素也会被后移,key 被插到它们前面,相等元素的相对顺序反而被改变,排序变成不稳定的。故说法错误。

第 7 题

下面递推程序计算 $n!$。当 n = 4 时,返回值为 24。

C++

int factorial(int n) {

int result = 1;

for (int i = 1; i <= n; i++) {

result *= i;

}

return result;

}

答案:√

知识点解析

本题考查用递推计算阶乘。

result 从 1 开始,循环依次乘以 1、2、3、4,得 4! = 1×2×3×4 = 24,函数逻辑正确。故说法正确。

第 8 题

下面两层循环的时间复杂度是 $O(n^2)$。

C++

for (int i = 0; i < n; i++) {

for (int j = 1; j < n; j *= 2) {

cout << i + j;

}

}

答案:×

知识点解析

本题考查循环变量增长方式对时间复杂度的影响。

外层循环执行 n 次;内层 j 从 1 开始每次乘 2(1, 2, 4, 8, …),执行约 log₂n 次,总执行次数约为 n·log₂n,时间复杂度是 O(n log n) 而非 O(n²)。分析复杂度要看循环变量如何增长:j *= 2 是对数级增长,不能想当然地按两层循环就是 O(n²)。故说法错误。

第 9 题

假设文件能够正常打开,下面程序会把 Welcome 写入 log.txt。

C++

int main() {

ofstream fout("log.txt");

fout << "Welcome";

fout.close();

return 0;

}

答案:√

知识点解析

本题考查文件输出流 ofstream 的基本用法。

ofstream fout(“log.txt”) 打开(不存在则自动创建)文件,fout << “Welcome” 把字符串写入文件,close() 关闭文件并保存内容。假设文件能正常打开,程序确实会把 Welcome 写入 log.txt。故说法正确。

第 10 题

执行下面程序时,catch (int e) 能够捕获由 throw "Error" 抛出的异常,因此程序输出 Caught。

C++

int main() {

try {

throw "Error";

} catch (int e) {

cout << "Caught";

}

return 0;

}

答案:×

知识点解析

本题考查异常类型匹配规则。

字符串字面量 “Error” 的类型是 const char*,throw 抛出的异常对象就是 const char* 类型,而 catch (int e) 只能捕获 int 类型的异常,类型不匹配、无法捕获;未被捕获的异常会使程序调用 std::terminate 异常终止,不会输出 Caught。故说法错误。

三、编程题(每题 25 分,共 50 分)

编程 1

新汉诺塔

时间限制 1.0 s 内存限制 512.0 MB

题目描述

汉诺塔问题是最经典的递推问题之一:

- 有三个可以放圆盘柱子,编号为 A、B 和 C。

- 开始时柱子 A 上套着 n 个圆盘,它们从上到下按照从小到大的顺序排列。

- 我们的任务是要把这 n 个圆盘移到柱子 C 上,并保持它们的原有顺序不变。

在移动圆盘的过程中,需要遵守以下规则:

1. 圆盘只能从一根柱子顶部拿出,从另一根柱子顶部放入。

2. 每次只能移动一个圆盘。

3. 小圆盘必须时刻位于大圆盘之上。

小杨在学习了汉诺塔问题后,决定添加一个新规则:

4. 每一次移动,圆盘只能从 A 移动到 B,从 B 移动到 C,或者从 C 移动到 A;其它移动是不允许的。

在新规则下,给定圆盘数量 n,试问最少移动步数是多少?

输入格式

输入一个正整数 n,表示圆盘的数量。

输出格式

输出一个整数,表示在新规则下将 n 个圆盘从 A 移动到 C 所需的最少移动步数。

样例

输入样例 1

2

输出样例 1

7

样例解释 1

以下步骤是最佳的(编号为 1 的是小盘,为 2 的是大盘):

1. 将 1 从 A 移动到 B;

2. 将 1 从 B 移动到 C;

3. 将 2 从 A 移动到 B;

4. 将 1 从 C 移动到 A;

5. 将 2 从 B 移动到 C;

6. 将 1 从 A 移动到 B;

7. 将 1 从 B 移动到 C。

可以证明没有更少步骤可以完成这个任务。

输入样例 2

3

输出样例 2

21

数据范围

对于所有数据,n ≤ 20。

参考程序(答案)

C++

#include <iostream>

using namespace std;

int f[22], g[22];

int main() {

int n;

cin >> n;

for (int i = 1; i <= n; ++i) {

f[i] = 2 * g[i - 1] + 1;

g[i] = 2 * g[i - 1] + f[i - 1] + 2;

}

cout << g[n] << endl;

return 0;

}

编程 2

有序网格

时间限制 1.0 s 内存限制 512.0 MB

题目描述

小 A 有一个 n 行 m 列格子组成的二维网格,从上到下依次是第 1 行到第 n 行,从左到右依次是第 1 列到第 m 列。

每个格子里有一个数字,第 i 行第 j 列的格子里的数字是 $a_{i,j}$。

小 A 想让二维网格变得有序,因此他先对每一行从左到右按升序排序,再对每一列从上到下按升序排序。以下是一个先完成行排序再完成列排序的例子:

原始网格:

文本

1 3 2 5

6 2 4 4

5 4 1 3

每行升序排序后:

文本

1 2 3 5

2 4 4 6

1 3 4 5

每列升序排序后:

文本

1 2 3 5

1 3 4 5

2 4 4 6

小 A 想知道二维网格经过以上排序后的结果。你能编写程序帮助他吗?

输入格式

第一行,两个正整数 n, m,分别二维网格的行数与列数。

接下来 n 行,每行 m 个整数 $a_{i,1}, \ldots, a_{i,m}$,表示二维网格中的数字。

输出格式

输出 n 行,每行 m 个整数,表示二维网格先完成行排序再完成列排序后的结果。

样例

输入样例 1

3 2

6 5

4 3

2 1

输出样例 1

1 2

3 4

5 6

输入样例 2

3 4

1 3 2 5

6 2 4 4

5 4 1 3

输出样例 2

1 2 3 5

1 3 4 5

2 4 4 6

数据范围

对于所有测试点,保证 2 ≤ n ≤ 10,2 ≤ m ≤ 10,1 ≤ $a_{i,j}$ ≤ 100。

参考程序(答案)

C++

#include <cstdio>

#include <algorithm>

using namespace std;

const int N = 15;

int n, m;

int a[N][N];

void sort_row(int n) {

for (int i = 1; i <= m; i++)

for (int j = 1; j < m; j++)

if (a[n][j] > a[n][j + 1]) {

int tmp = a[n][j];

a[n][j] = a[n][j + 1];

a[n][j + 1] = tmp;

}

return;

}

void sort_col(int m) {

for (int i = 1; i <= n; i++)

for (int j = 1; j < n; j++)

if (a[j][m] > a[j + 1][m]) {

int tmp = a[j][m];

a[j][m] = a[j + 1][m];

a[j + 1][m] = tmp;

}

return;

}

int main() {

scanf("%d%d", &n, &m);

for (int i = 1; i <= n; i++)

for (int j = 1; j <= m; j++)

scanf("%d", &a[i][j]);

for (int i = 1; i <= n; i++)

sort_row(i);

for (int i = 1; i <= m; i++)

sort_col(i);

for (int i = 1; i <= n; i++)

for (int j = 1; j <= m; j++)

printf("%d%c", a[i][j], " \n"[j == m]);

return 0;

}

由于工作量较大,若存在错漏欢迎大家评论区指正。祝各位考生顺利通过!觉得有用,欢迎点赞、在看、转发三连。

相关学习资料