2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向)

四季读书网 2 0
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向)
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第1张
一、单选题(每题2分,共30 
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第2张

第 题 执行下面程序后,输出为( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第3张

A. 2 12

B. 6 12

C. 6 4

D. 12 6

【答案】B

【考纲知识点】C++ 默认参数

函数定义时给形参指定默认值,调用时若不传入实参,就使用默认值;若传入实参,就使用传入的值。

【解析】

本题中 f(int x = 2) 表示:

调用 f() 时,x 取默认值 2

调用 f(4) 时,x 取传入的 4

f():x = 2 → return 2 * 3 = 6

f(4):x = 4 → return 4 * 3 = 12

输出:6 12

第 题 执行下面代码后,输出为( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第4张

A. 5 5

B. 12 12

C. 12 5

D. 5 12

【答案】B

【考纲知识点】C++ 多级指针(指针的指针)

【解析】

int* p = &a:p 是一级指针,存储 a 的地址,*p 等价于 a

int** q = &p:q 是二级指针,存储 p 的地址,*q 等价于 p,**q 等价于 *p 等价于 a

**q += 7:等价于 a += 7,直接修改 a 的值

计算过程

1、初始:a = 5

2、p 指向 a,q 指向 p

3、**q += 7 → a = 5 + 7 = 12

4、cout << a << " " << *p:a = 12,*p 是 a 的值,也为 12

5、输出:12 12

多级指针内存布局示意图 + 详细解析

一、初始状态(执行 **q +=7 之前)

内存地址 + 存储内容的方式,直观展示变量、指针的关系:

变量名
类型
内存地址(示例)
存储内容
含义说明
aint0x1005
普通整型变量,值为 5
pint*0x2000x100
一级指针,存储a的地址
qint**0x3000x200
二级指针,存储p的地址

二、执行 **q +=7 后的状态

**q 最终指向 a,因此 **q +=7 等价于 a = a +7
变量名
类型
内存地址(示例)
存储内容
变化说明
aint0x10012
原值 5+7=12,被直接修改
pint*0x2000x100
指针本身不变,仍指向a
qint**0x3000x200
指针本身不变,仍指向p
三、逐行代码对应内存变化
1、int a = 5;
→ 开辟内存 0x100,存入 5
2、int* p = &a;
→ 开辟内存 0x200,存入 a 的地址 0x100
3、int** q = &p;
→ 开辟内存 0x300,存入 p 的地址 0x200
4、**q += 7;
→ *q 解引用 q,得到 p(地址0x200的内容0x100
→ **q 解引用 *q,得到 a(地址0x100的内容5
→ **q +=7 → a = 5+7=12,修改0x100的内容为12
5、cout << a << " " << *p;
→ a 是12*p 解引用p,得到a的值12
→ 输出 12 12
四、核心知识点总结

1、* 是解引用运算符:*指针 = 指针指向的变量

*p = a

*q = p

**q = *(*q) = *p = a

2、指针本身的地址永远不变:p永远存a的地址,q永远存p的地址,修改的是a的值

3、多级指针的本质:层层指向,最终指向原变量,修改最内层就是修改原变量

第 题 已知:

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第5张

则表达式 *(*(p + 2) + 1) 的值为( )。

A. 6

B. 10

C. 9

D. 11

【答案】B

【考纲知识点】数组指针(行指针)与二维数组的指针运算

1、int (*p)[4] 是数组指针:指向一个长度为 4 的int数组,也就是二维数组的一行。

2、二维数组名a是指向第 0 行的数组指针,p = a 表示p初始指向第 0 行。

3、指针运算规则:

(1)p + n:向后偏移n行(因为p是行指针,步长为一行的大小,即4*sizeof(int))

(2)*(p + n):解引用后得到第n行的首元素地址(等价于a[n])

(3)*(p + n) + m:在第n行首地址基础上,向后偏移m个元素(等价于&a[n][m])

(4)*(*(p + n) + m):解引用后得到a[n][m]的值

【解析】

1、p 初始指向第 0 行

2、p + 2:向后偏移 2 行 → 指向第 2 行(元素{9,10,11,12})

3、*(p + 2):解引用,得到第 2 行的首元素地址(等价于a[2],指向9)

4、*(p + 2) + 1:在第 2 行首地址基础上,向后偏移 1 个元素 → 指向10(等价于&a[2][1])

5、*(*(p + 2) + 1):解引用,得到10

数组指针(*p)[4]:行指针,p+n跳行,*(p+n)+m跳列,*(*(p+n)+m)a[n][m]

第 题 执行下面程序后,输出为( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第6张

A. 2 3 4

B. 1 3 4

C. 2 1 4

D. 1 1 1

【答案】B

【考纲知识点】C++ 函数参数传递:值传递、引用传递、指针传递

值传递(int a):形参是实参的拷贝,修改形参不影响实参

引用传递(int &b):形参是实参的别名,修改形参直接修改实参

指针传递(int *c):形参是实参地址的拷贝,解引用*c修改的是实参本身

【解析】

1、初始:x=1, y=1, z=1

2、调用fun(x, y, &z):

a = x(值传递,a=1,a +=1 → a=2,但x不变,仍为1)

b = y(引用传递,b是y的别名,b +=2 → y=1+2=3)

c = &z(指针传递,*c是z,*c +=3 → z=1+3=4)

3、输出:x=1, y=3, z=4 → 1 3 4

参数传递:值传递不改实参,引用 / 指针传递改实参

第 题 执行下面程序后输出为( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第7张

A. 12 3

B. 10 5

C. 12 5

D. 10 3

【答案】A

【考纲知识点】全局变量、局部变量、作用域解析符::、引用传递

全局变量x:定义在main外,作用域为整个程序,初始值3

局部变量x:定义在main内,作用域为main函数,初始值10,会屏蔽全局变量x

作用域解析符::x:强制访问全局变量x,不受局部变量影响

引用传递f(int& x):形参x是实参(main内的局部x)的别名,修改形参就是修改实参

【解析】

1、全局x初始:3

2、main内定义局部x:10(屏蔽全局x)

3、调用f(x):传入main内的局部x,引用传递

f内x +=2 → 局部x = 10 + 2 = 12

4、输出:

x:main内的局部x,值为12

::x:全局x,未被修改,值为3

5、最终输出:12 3

作用域解析符::   :::x强制访问全局变量,不受局部变量屏蔽

第 题 下列关于结构体初始化的写法,正确的是( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第8张
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第9张

【答案】B

【考纲知识点】C++ 结构体初始化语法

(1)结构体是聚合类型,标准初始化方式是大括号 {} 列表初始化

(2)圆括号 () 用于构造函数调用,普通结构体无自定义构造函数时不能用

(3)new 用于动态分配内存,返回的是指针类型,不能直接赋值给结构体变量

(4)尖括号 <> 用于模板参数,不是初始化语法

【解析】

A 选项:❌ 错误。圆括号 () 不能用于结构体聚合初始化,语法非法

B 选项:✅ 正确。大括号 {} 是 C++ 标准的聚合初始化语法,Point p = {1,2} 会按顺序初始化 x=1, y=2

C 选项:❌ 错误。new 返回 Point*(指针),不能赋值给 Point 类型变量,且普通结构体无构造函数 Point(1,2)

D 选项:❌ 错误。尖括号 <> 不是初始化语法,完全非法

结构体初始化:只有大括号 {} 是标准聚合初始化,()/<>/new 都不对

第 题 执行下面代码后输出为( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第10张

A. 11 12

B. 1 12

C. 11 2

D. 1 2

【答案】B

【考纲知识点】结构体的参数传递:值传递 vs 引用传递

值传递(void g(S s)):形参 s 是实参的拷贝,修改形参的 a 不会影响原结构体

引用传递(void h(S& s)):形 s 是原结构体的别名,修改 s.b 就是修改原结构体的 b

【解析】

1、初始:s.a = 1,s.b = 2

2、调用 g(s):值传递,形参 s 是拷贝,s.a +=10 只修改拷贝,原 s.a 仍为 1

3、调用 h(s):引用传递,形参 s 是原结构体,s.b +=10 → 原 s.b = 2+10=12

4、输出:s.a = 1,s.b = 12 → 1 12

结构体传参:值传递不改原结构体,引用传递改原结构体

第 题 关于递推算法的描述,正确的是( )。

A. 递推表现为函数自己调用自己

B. 递推从已知初值出发,利用递推关系逐步推出后续结果

C. 递推只能用于指数复杂度问题

D. 递推一定需要回溯

【答案】B

【考纲知识点】递推算法的定义与特点

1、递推:从已知的初始值出发,通过固定的递推关系(公式),一步步计算出后续所有结果,是迭代式的,无函数自调用

2、递归:函数自己调用自己,是递推的逆过程(从问题到子问题)

3、递推的时间复杂度通常是线性的(如斐波那契数列递推 O(n)),不是指数级

4、递推是正向计算,不需要回溯(回溯是递归 / 深度优先搜索的特性)

【解析】

A 选项:❌ 错误。函数自调用是递归,不是递推

B 选项:✅ 正确。递推的核心就是「从初值出发,用递推关系逐步计算」

C 选项:❌ 错误。递推通常用于线性复杂度问题,不是指数级

D 选项:❌ 错误。递推是正向迭代,不需要回溯

递推 vs 递归:递推是「从初值往后算」,递归是「函数自己调自己」

第 题 执行 climb(6) 的返回值为( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第11张

A. 8

B. 13

C. 5

D. 10

【答案】B

【考纲知识点】递推算法(斐波那契型爬楼梯问题)

递推关系:f(n) = f(n-1) + f(n-2),初始条件 f(1)=1, f(2)=2

代码逻辑:用 a,b 存储前两项,c 计算当前项,循环迭代计算

【解析】

初始:a=1, b=2(对应 f(1)=1, f(2)=2)

i=3:c=1+2=3 → a=2, b=3(f(3)=3)

i=4:c=2+3=5 → a=3, b=5(f(4)=5)

i=5:c=3+5=8 → a=5, b=8(f(5)=8)

i=6:c=5+8=13 → a=8, b=13(f(6)=13)

最终返回 c=13

递推爬楼梯f(n)=f(n-1)+f(n-2),初始f(1)=1,f(2)=2

第 10 题 某排序算法对如下数据排序(按 score 升序),则下面关于该排序算法稳定性的描述中,说法正确的是()。

初始: (90,'A'), (90,'B'), (80,'C'), (90,'D')

排序后: (80,'C'), (90,'A'), (90,'B'), (90,'D')

A. 不稳定,因为出现了相同分数

B. 稳定,因为相同 score 的相对顺序保持为 在 前、在 

C. 不稳定,因为 跑到前面了

D. 无法判断

【答案】B

【考纲知识点】排序算法的稳定性定义

稳定排序:相等元素的相对顺序在排序前后保持不变

本题中,score=90 的元素初始顺序:A → B → D

排序后顺序:A → B → D,相对顺序完全不变

C 分数不同,位置变化不影响稳定性

【解析】

A 选项:❌ 错误。相同分数本身不代表不稳定,关键是相对顺序是否改变

B 选项:✅ 正确。相同score的相对顺序完全保留,符合稳定排序定义

C 选项:❌ 错误。C分数不同,位置变化不影响稳定性

D 选项:❌ 错误。可以明确判断为稳定

稳定排序:相等元素相对顺序不变,不同元素位置变化不影响

第 11 题 下面代码试图把数组按升序进行插入排序,横线处应填写( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第12张

A. a[j] < key

B. a[j] > key

C. a[j+1] > key

D. a[j] == key

【答案】B

【考纲知识点】直接插入排序的核心逻辑

升序插入排序:将key插入到前面已排序的序列中

循环条件:当a[j] > key时,说明key应该在a[j]前面,将a[j]后移

直到找到a[j] ≤ key的位置,将key插入到j+1位置

【解析】

A 选项:❌ 错误。a[j] < key 会导致循环不执行,无法后移元素

B 选项:✅ 正确。a[j] > key 时,将a[j]后移,继续向前查找

C 选项:❌ 错误。a[j+1] 是待插入位置,不是已排序元素

D 选项:❌ 错误。相等元素不影响升序,无需后移

插入排序:升序条件a[j] > key,后移元素,插入key

第 12 题 下列代码段的时间复杂度为( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第13张
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第14张

【答案】C

【考纲知识点】时间复杂度分析

【解析】

双层嵌套循环,外层循环n次,内层循环n次

总执行次数:n × n = n²,if判断不影响时间复杂度量级

时间复杂度为O(n²)

时间复杂度:双层循环O(n²),单层O(n)

第 13 题 下面哪种方式不能实现将字符串 Welcome to 2026! 输出重定向到文件 log.txt ( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第15张
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第16张

【答案】B

【考纲知识点】C++ 输出重定向的方法

方法 1:freopen重定向stdout(A 选项):将标准输出重定向到文件,cout输出到文件

方法 2:修改cout的rdbuf(C 选项):将cout的缓冲区绑定到文件流,输出到文件

方法 3:直接用文件流输出(D 选项):outFile << "..." 直接写入文件

B 选项:仅创建文件流,未修改cout的输出目标,cout仍输出到控制台,文件无内容

【解析】

A 选项:✅ 正确。freopen重定向stdout,cout输出到文件

B 选项:❌ 错误。仅创建outFile,未修改cout的输出,cout仍输出到控制台,文件无内容

C 选项:✅ 正确。修改cout的rdbuf,将cout输出重定向到文件

D 选项:✅ 正确。直接用文件流outFile输出到文件

输出重定向freopen/rdbuf/ 直接文件流,仅创建文件流不重定向cout

第 14 题 执行下面程序,输出结果是( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第17张

A. A

B. B

C. 程序崩溃

D. 无输出

【答案】B

【考纲知识点】C++ 异常处理(throw /try/catch)

1、throw 抛出异常的类型,必须和 catch 的参数类型严格匹配才能被捕获

2、本题中 throw 0 抛出的是 int 类型的异常

3、第一个 catch(const char*) 类型不匹配,无法捕获;第二个 catch(int) 类型匹配,成功捕获

【解析】

执行流程

1、调用 divi(10,0),b=0 触发 throw 0,抛出 int 类型异常

2、try 块中抛出异常,程序跳转到 catch 块匹配

3、第一个 catch(const char*) 类型不匹配,跳过

4、第二个 catch(int) 类型匹配,执行 cout << "B"

5、最终输出 B

异常捕获throw 的类型必须和 catch 的参数类型严格匹配,否则无法捕获

第 15 题 下列函数实现排行榜中单个元素的位置调整(类似插入排序的相邻搬移)。当某玩家分数增加,需将其向前移动时, while 循环的条件应为( )。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第18张

A. i > 0 && cur.score > players[i-1].score

B. i > 0 && cur.score < players[i-1].score

C. i < n-1 && cur.score > players[i+1].score

D. i < n-1 && cur.score < players[i+1].score

【答案】A

【考纲知识点】插入排序的向前移动逻辑(排行榜分数调整)

【解析】

1、需求:玩家分数增加,需要向前(数组头部方向)移动,保持排行榜按分数从高到低排序

2、代码逻辑:

(1)cur 存储当前要移动的玩家

(2)i 初始为当前玩家的下标 idx

(3)循环中执行 players[i] = players[i-1]:将前一个玩家向后移动一位

(4)i--:继续向前比较

(5)循环结束后,将 cur 插入到正确位置

3、逐项验证

A 选项:✅ 正确。i>0 保证不越界,cur.score > players[i-1].score 保证高分向前移动

B 选项:❌ 错误。cur.score < players[i-1].score 会让低分向前移动,不符合需求

C 选项:❌ 错误。i < n-1 是向后移动的边界,方向错误

D 选项:❌ 错误。方向和比较条件均错误,是向后移动的逻辑

插入排序移动:向前移动的边界是 i>0,高分向前的条件是 cur.score > 前一个元素.score

二、判断题(每题2分,共20
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第19张

第 题 下面代码执行结束时,变量 的值变成 15

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第20张

【答案】√

【考纲知识点】引用传递(Reference)

【解析】

int &x 是引用,它是变量 a 的别名

在函数 add10 中对 x 的修改,直接作用于实参 a。

计算过程

  1. 初始:a = 5
  2. 调用 add10(a)x 是 a 的别名,x += 10 → a = 5 + 10 = 15
  3. 最终 a = 15

第 题 引用一旦绑定某个变量,就不能再绑定其他变量。( )

【答案】

【考纲知识点】引用的特性

1、引用在定义初始化时必须绑定一个变量。

2、一旦绑定成功,终身绑定,无法再改变它的指向(即不能重新绑定另一个变量)。

3、赋值运算符 = 改变的是变量的值,不是引用的绑定关系。

第 题 执行下面代码,输出结果为 

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第21张

【答案】×

【考纲知识点】二维数组的内存布局与指针运算

1、内存布局:二维数组 a[2][3] 在内存中是连续存放的,顺序如下:
地址
元素
...
a[0][0]
...
a[0][1]
...
a[0][2]
...
a[1][0]
...
a[1][1]
...
a[1][2]
2、指针减法规则:两个 int* 相减,结果是它们之间间隔的元素个数,而不是字节数。

【解析】

1、&a[0][1] 指向第 0 行第 1 列。

2、&a[1][2] 指向第 1 行第 2 列。

3、两者中间的元素个数:

(1)从 a[0][1] 往后数:a[0][2] (1 个) -> a[1][0] (2 个) -> a[1][1] (3 个) -> a[1][2] (4 个)。

(2)总共有 个元素间隔。

4、结果是 4,不是 5。

第 题 下面程序可以正常编译并输出 10 

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第22张

【答案】×

【考纲知识点】函数重载与默认参数的二义性

C++ 中函数重载要求参数列表(类型、数量、顺序)不同

【解析】

本题中存在两个 calc 函数,一个单参数,一个双参数。

调用 calc(5) 时:

候选 1:calc(int x) -> 完美匹配,参数 5 -> 返回 10。

候选 2:calc(int x, int y = 10) -> 也可以匹配,使用默认参数 y=10,返回 50。

编译器在匹配时发现有两个完全匹配的重载版本,会报 “对重载函数的调用不明确”*的编译错误,无法通过编译。

第 题 下面程序执行后输出 2010 

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第23张

【答案】

【考纲知识点】作用域与函数调用语法

1、全局变量 x:定义在 main 函数外,作用域为整个程序,初始值为 10。

2、局部变量 x:定义在 f() 函数内部,作用域仅在 f() 内,初始值为 20,会屏蔽全局变量 x。

3、作用域规则:在函数内部访问变量时,优先使用局部变量,若没有局部变量,才会使用全局变量。

【解析】

1、全局变量初始化:int x = 10; → 全局 x 赋值为 10。

2、main 函数执行:

(1)调用 f() 函数:

f() 内定义局部变量 int x = 20; → 局部 x 赋值为 20,屏蔽全局 x。

cout << x; → 输出局部变量 x 的值 20。

(2)f() 执行完毕,回到 main 函数。

(3)cout << x; → main 内无局部 x,使用全局 x,输出 10。

3、最终输出:2010,与题目描述完全一致。

第 题 在 C++ 中,如果声明了一个指针变量但没有显式初始化,该指针会自动被初始化为 nullptr 

【答案】×

【考纲知识点】指针的初始化规则

1、局部指针(函数内部定义):不会自动初始化,值是随机的垃圾值(野指针)。
2、全局指针 或 静态指针static):会自动初始化为 nullptr

【解析】题目未说明作用域,默认按局部指针处理,因此不会自动初始化为 nullptr

第 题 下面代码没有语法错误。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第24张

【答案】

【考纲知识点】C++ 嵌套结构体定义语法

C++ 允许在结构体内部定义嵌套结构体(成员结构体),并直接声明该类型的成员变量。

【解析】

代码中:
  • 嵌套struct Equipment,并声明成员equipment
  • 嵌套struct Skill,并声明数组成员skills[8]
  • 所有成员定义符合语法,无语法错误

第 题 下面程序能够把 Hello 写入 data.txt 文件中。

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第25张

【答案】×

【考纲知识点】C++ 文件输出流与标准输出的区别

ofstream fout是文件输出流,用于向文件写入数据

cout是标准输出流,默认输出到控制台(屏幕)

【解析】代码中仅创建了文件流,但没有用fout输出内容cout的输出仍在控制台,不会写入文件

第 题 由于选择排序和插入排序的时间复杂度均为O(),在任何实际场景下两者的性能表现几乎相同,可以互相替代。

【答案】×

【考纲知识点】选择排序 vs 插入排序的性能差异

【解析】

时间复杂度:两者最坏 / 平均时间复杂度均为O(),但实际性能差异极大

1、插入排序:最好情况(已排序数组)时间复杂度为O(n),且仅在需要移动时赋值,缓存友好,实际运行速度远快于选择排序

2、选择排序:无论数组是否有序,都需要完整遍历,交换次数固定,性能远差于插入排序

实际场景:插入排序在小规模、部分有序数据中性能远优于选择排序,不可互相替代

第 10 题 下面用递推方式计算斐波那契数列第 项的程序,时间复杂度是 

O(2ⁿ)

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第26张

【答案】×

【考纲知识点】递推 vs 递归的时间复杂度

【解析】

1、本题是递推(迭代)实现:单层for循环,循环次数为n−1次,时间复杂度为O(n)

2、O(2ⁿ)是递归实现(fib(n)=fib(n-1)+fib(n-2),无记忆化)的时间复杂度,与本题递推实现无关
三、编程题(每题 25 分,共 50 分)

3.1 编程题 1

试题名称:山之谷

时间限制1.0 s

内存限制512.0 MB

3.1.1 题目描述

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第27张
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第28张

方法一:方向数组版(8 个方向偏移量 + 越界判断)

【考纲知识点】

1、网格遍历:遍历二维网格的每个单元格

2、8 邻域判断:检查每个单元格的 8 个相邻方向(上下左右 + 4 个斜向)

3、边界处理:处理网格边缘 / 角落单元格,避免越界访问

4、条件判断:验证当前单元格海拔 ≤ 所有相邻单元格海拔

【解题思路】

1、输入数据:读取网格大小 N, M,以及 N×M 的海拔矩阵 h

2、定义 8 个方向:用方向数组存储 8 个相邻方向的坐标偏移量 { {-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1} }

3、遍历每个单元格:

(1)对每个 (i,j),获取当前海拔 cur = h[i][j]

(2)遍历 8 个方向,计算相邻坐标 (ni, nj)

(3)检查 (ni, nj) 是否在网格范围内(0 ≤ ni < N 且 0 ≤ nj < M)

(4)若相邻坐标合法,判断 cur > h[ni][nj]:只要有一个相邻单元格海拔更低,当前单元格就不是山谷,直接跳出判断

4、统计山谷数量:若当前单元格满足「海拔 ≤ 所有合法相邻单元格海拔」,计数器 + 1

5、输出结果:打印最终计数器值

【参考程序】

#include<iostream>#include<vector>usingnamespace std;intmain(){    int N, M;    cin >> N >> M;    vector<vector<int>> h(N, vector<int>(M));    // 读取海拔矩阵    for (int i = 0; i < N; ++i) {        for (int j = 0; j < M; ++j) {            cin >> h[i][j];        }    }    // 8个方向的坐标偏移:左上、上、右上、左、右、左下、下、右下    int dx[] = {-1-1-100111};    int dy[] = {-101-11-101};    int count = 0;    // 遍历每个单元格    for (int i = 0; i < N; ++i) {        for (int j = 0; j < M; ++j) {            int cur = h[i][j];            bool is_valley = true;            // 检查8个相邻方向            for (int k = 0; k < 8; ++k) {                int ni = i + dx[k];                int nj = j + dy[k];                // 检查坐标是否在网格内                if (ni >= 0 && ni < N && nj >= 0 && nj < M) {                    // 若相邻单元格海拔更低,不是山谷                    if (cur > h[ni][nj]) {                        is_valley = false;                        break;                    }                }            }            if (is_valley) {                count++;            }        }    }    cout << count << endl;    return0;}
方法2:哨兵边界 + 双层循环遍历 8 邻域

【考纲知识点】

1. 8 邻域遍历

(1)一个单元格的 8 个相邻方向:上下左右 + 4 个斜向(左上、右上、左下、右下),共 8 个方向。

(2)代码用双层循环 i2 ∈ [i-1, i+1]、j2 ∈ [j-1, j+1] 一次性遍历所有 8 个方向,写法简洁高效。

2. 哨兵边界(虚拟边界)技巧

问题:网格边缘 / 角落的单元格,部分相邻方向在网格外,直接访问会数组越界,需要额外做越界判断。

解决方案:给网格加一圈「哨兵边界」,赋值为极大值(1e9)。

山谷的条件是「当前值 ≤ 所有相邻值」,边界外的极大值永远满足条件,不会影响判断。

彻底省去了越界判断,代码更简洁、不易出错。

3. 山谷判断逻辑

山谷定义:当前单元格的海拔 ≤ 所有相邻单元格的海拔。

代码逻辑:只要有一个相邻单元格的海拔 < 当前单元格,就不是山谷;只有所有相邻单元格海拔 ≥ 当前单元格,才是山谷。

注意:循环包含了当前单元格自己(i2=i, j2=j),h[i][j] > h[i][j] 永远为 false,不影响结果,无需额外排除。

4. 数组索引设计

代码从 i=1, j=1 开始存储数据,而不是从 0 开始,就是为了给 0 行、0 列、n+1 行、m+1 列留出哨兵边界的空间,完美适配 8 邻域遍历。

【解题思路】

步骤 1:输入与数组初始化

读取网格大小 n, m,读取 n×m 的海拔数据,存入数组 h[1..n][1..m]。

步骤 2:添加哨兵边界

给数组的第 0 行、第 0 列、第 n+1 行、第 m+1 列,全部赋值为极大值 1e9,作为虚拟边界。

步骤 3:遍历每个单元格

双层循环遍历网格内的每一个单元格 (i,j)。

步骤 4:8 邻域检查

对每个单元格,遍历其 8 个相邻方向,检查是否满足「当前海拔 ≤ 所有相邻海拔」。

只要有一个相邻海拔 < 当前海拔,就标记为非山谷,跳出循环。

步骤 5:统计并输出结果

统计所有满足条件的山谷数量,输出结果。

// 引入标准输入输出头文件#include<iostream>usingnamespace std;intmain(){    // 定义网格的行数n、列数m    int n, m;    // 定义海拔数组h,大小105×105(满足数据范围n,m≤100的要求,留足边界空间)    int h[105][105];    // 读取网格大小n、m    cin >> n >> m;    // 双层循环读取n行m列的海拔数据    // 这里从i=1、j=1开始存,是为了给网格加一圈"哨兵边界",避免越界判断    for(int i = 1; i <= n; i++)        for(int j = 1; j <= m; j++)            cin >> h[i][j];    // 给网格加一圈"哨兵边界",赋值为极大值1e9    // 作用:让网格边缘/角落的单元格,在检查8邻域时,边界外的"虚拟单元格"不会影响山谷判断    // 因为山谷要求当前值≤所有相邻值,边界外的极大值永远满足条件,无需额外做越界判断    for(int i = 0; i <= max(n, m) + 1; i++)        h[i][0] = h[0][i] = h[i][m + 1] = h[n + 1][i] = 1e9;    // 定义山谷计数器ans,初始为0    int ans = 0;    // 双层循环遍历网格内的每一个单元格(i从1到n,j从1到m)    for(int i = 1; i <= n; i++)        for(int j = 1; j <= m; j++) {            // 标记当前单元格是否为山谷,初始假设为true(是山谷)            bool ok = true;            // 双层循环遍历当前单元格的8个相邻方向(i2从i-1到i+1,j2从j-1到j+1)            // 包含:左上(i-1,j-1)、上(i-1,j)、右上(i-1,j+1)、左(i,j-1)、当前(i,j)、右(i,j+1)、左下(i+1,j-1)、下(i+1,j)、右下(i+1,j+1)            for(int i2 = i - 1; i2 <= i + 1; i2++)                for(int j2 = j - 1; j2 <= j + 1; j2++)                    // 核心判断:如果当前单元格海拔 > 相邻单元格海拔 → 不满足山谷条件                    // 注意:包含当前单元格自己(i,j),h[i][j] > h[i][j] 永远为false,不影响结果                    if(h[i][j] > h[i2][j2]) {                        ok = false;  // 标记为非山谷                        break;       // 一旦不满足,直接跳出循环,无需继续检查                    }            // 如果ok为true,说明当前单元格是山谷,计数器+1            ans += ok;        }    // 输出山谷总数    cout << ans;    return0;}

代码优势

1、无越界判断:用哨兵边界彻底解决了边缘单元格的越界问题,代码更简洁。

2、遍历高效:用双层循环一次性遍历 8 个方向,无需手动写 8 个方向的偏移量。

3、逻辑严谨:包含当前单元格的判断不影响结果,无需额外处理。

⚠️ 易错点

1、哨兵边界赋值错误:如果给边界赋值为极小值(如 0),会导致边缘单元格误判为山谷。

2、索引范围错误:如果从 0 开始存数据,边界赋值会覆盖有效数据,导致结果错误。

3、判断条件写反:把 h[i][j] > h[i2][j2] 写反,会把山谷判断成山峰,结果完全错误。

4、漏写 break:如果不写 break,会继续遍历后续方向,浪费时间,不影响结果但效率低。

是更简洁、更适合刷题的写法

3.2 编程题 2

试题名称:礼盒排序

时间限制1.0 s

内存限制512.0 MB

3.2.1 题目描述

商店推出了许多礼盒,每个礼盒中包含 件商品,每件商品都有一个价格。

现在需要对这些礼盒进行排序,排序规则如下:

1. 先按礼盒总价格从小到大排序;

2. 如果总价格相同,按礼盒中最贵商品的价格从小到大排序;

3. 如果仍然相同,按礼盒中最便宜商品的价格从小到大排序;

4. 如果仍然相同,按礼盒编号从小到大排序。

请输出排序后的礼盒编号。

3.2.2 输入格式

第一行包含两个整数 n和k ,分别表示礼盒数量和每个礼盒中商品的数量。

接下来n 行,每行包含 k个整数,第i 行表示第 i个礼盒中各商品的价格

3.2.3 输出格式

输出一行,包含排序后的礼盒编号(编号从 开始),用空格分隔。

3.2.4 样例

2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第29张

【考纲知识点】

1. 结构体(struct)封装数据

  • (1)作用:将每个礼盒的总价、最高价、最低价、编号这 4 个关联数据,打包成一个Combo结构体,方便统一管理、排序。
  • (2)优势:避免用多个独立数组存储数据,代码更简洁、逻辑更清晰,排序时直接操作结构体对象即可。

2. 自定义排序规则(sort + 比较函数)

  • (1)C++ 的std::sort函数默认按升序排序基本类型(如 int),对于自定义结构体,需要手动提供比较函数cmp,定义排序规则。
  • (2)比较函数规则:cmp(a,b)返回true时,表示a应该排在b的前面。
  • (3)本题的 4 级优先级规则:
    1. 总价sum升序 → 2. 最高价mx升序 → 3. 最低价mn升序 → 4. 编号id升序
  • (4)代码中通过依次判断、逐级降级的方式实现多关键字排序:前一个条件不相等时,直接按该条件排序;相等时,进入下一个条件判断。

3. 最大值 / 最小值的初始化技巧

  • (1)最高价mx初始化为-1:因为题目保证商品价格≥1,所以第一个商品的价格一定大于-1,能正确更新最大值。
  • (2)最低价mn初始化为1e9(极大值):第一个商品的价格一定小于1e9,能正确更新最小值。
  • (3)替代方案:也可以用<climits>头文件的INT_MIN(int 最小值)和INT_MAX(int 最大值),效果完全一致。

4. 输入输出格式控制

  • 输出编号时,通过if (i + 1 < n) cout << " ";控制空格:仅在非最后一个元素后输出空格,避免末尾出现多余空格,符合题目输出要求。

【解题思路】

步骤 1:数据结构设计

  • 定义Combo结构体,存储每个礼盒的总价 sum、最高价 mx、最低价 mn、编号 id,为排序做准备。

步骤 2:数据读取与预处理

  • 读取礼盒数量n和商品数k
  • 遍历每个礼盒:
    • 初始化 sum、mx、mn、id。
    • 读取 k 件商品的价格,累加计算总价,同时更新最高价和最低价。

步骤 3:自定义排序

  • 实现cmp比较函数,严格按照题目要求的 4 级优先级,定义礼盒的排序规则。
  • 调用sort函数,对所有礼盒按cmp规则排序。

步骤 4:结果输出

  • 遍历排序后的礼盒,依次输出编号,用空格分隔,最后换行。

【参考程序】

// 引入标准输入输出头文件#include<iostream>// 引入vector容器头文件,用于存储礼盒数据#include<vector>// 引入algorithm头文件,用于sort排序函数#include<algorithm>using namespace std;// 定义礼盒结构体Combo,封装每个礼盒的核心信息struct Combo {    int sum;  // 礼盒中所有商品的总价    int mx;   // 礼盒中最贵商品的价格(最大值max)    int mn;   // 礼盒中最便宜商品的价格(最小值min)    int id;   // 礼盒的编号(从1开始)};// 自定义排序比较函数cmp,严格遵循题目要求的4级排序规则// 用于sort函数,决定两个礼盒a和b的先后顺序boolcmp(const Combo &a, const Combo &b){    // 规则1:先按总价从小到大排序    if (a.sum != b.sum) return a.sum < b.sum;    // 规则2:总价相同,按最贵商品价格从小到大排序    if (a.mx != b.mx) return a.mx < b.mx;    // 规则3:仍相同,按最便宜商品价格从小到大排序    if (a.mn != b.mn) return a.mn < b.mn;    // 规则4:仍相同,按礼盒编号从小到大排序    return a.id < b.id;}intmain(){    // n:礼盒数量,k:每个礼盒中商品的数量    int n, k;    // 读取输入的n和k    cin >> n >> k;    // 定义vector容器v,存储n个Combo类型的礼盒对象    vector<Combo> v(n);    // 遍历每个礼盒(i从0到n-1,对应第1到第n个礼盒)    for (int i = 0; i < n; i++) {        // 初始化当前礼盒的总价为0        v[i].sum = 0;        // 初始化最贵商品价格为-1(因为商品价格≥1,保证第一个商品能更新最大值)        v[i].mx = -1;        // 初始化最便宜商品价格为1e9(极大值,保证第一个商品能更新最小值)        v[i].mn = 1e9;        // 礼盒编号为i+1(题目要求编号从1开始)        v[i].id = i + 1;        // 遍历当前礼盒的k件商品        for (int j = 0; j < k; j++) {            int x;            // 读取当前商品的价格x            cin >> x;            // 累加总价            v[i].sum += x;            // 更新最贵商品价格:取当前最大值和x的较大值            v[i].mx = max(v[i].mx, x);            // 更新最便宜商品价格:取当前最小值和x的较小值            v[i].mn = min(v[i].mn, x);        }    }    // 调用sort函数,对vector容器v中的礼盒,按自定义cmp规则排序    sort(v.begin(), v.end(), cmp);    // 遍历排序后的礼盒,输出编号    for (int i = 0; i < n; i++) {        // 输出当前礼盒的编号        cout << v[i].id;        // 如果不是最后一个元素,输出空格分隔(避免末尾多余空格)        if (i + 1 < n) cout << " ";    }    // 输出换行    cout << endl;    return 0;}

【扩展说明】

重载operator<替代比较函数

如果不想写单独的cmp函数,也可以直接在结构体中重载<运算符,效果完全等价:

struct Combo {    int sum, mx, mn, id;    bool operator<(const Combo& b) const {        if (sum != b.sumreturn sum < b.sum;        if (mx != b.mx) return mx < b.mx;        if (mn != b.mn) return mn < b.mn;        return id < b.id;    }};
此时sort(v.begin(), v.end())即可直接排序,无需传入cmp函数,代码更简洁。
#include<iostream>#include<vector>#include<algorithm>using namespace std;// 礼盒结构体struct Combo {    int sum;  // 总价    int mx;   // 最贵商品    int mn;   // 最便宜商品    int id;   // 编号    // 👇 在这里重载小于号 <    // 以后 sort 会自动用这个规则    bool operator<(const Combo& other) const {        // 规则1:总价小的在前        if (sum != other.sum)            return sum < other.sum;        // 规则2:总价相同,最贵商品小的在前        if (mx != other.mx)            return mx < other.mx;        // 规则3:仍相同,最便宜商品小的在前        if (mn != other.mn)            return mn < other.mn;        // 规则4:都相同,编号小的在前        return id < other.id;    }};intmain(){    int n, k;    cin >> n >> k;    vector<Combo> v(n);    for (int i = 0; i < n; i++) {        v[i].sum = 0;        v[i].mx = -1;        v[i].mn = 1e9;        v[i].id = i + 1;        for (int j = 0; j < k; j++) {            int x;            cin >> x;            v[i].sum += x;            v[i].mx = max(v[i].mx, x);            v[i].mn = min(v[i].mn, x);        }    }    // 👇 最简洁的地方:不用传 cmp 了!    sort(v.begin(), v.end());    // 输出编号    for (int i = 0; i < n; i++) {        cout << v[i].id;        if (i != n - 1) cout << " ";    }    cout << endl;    return 0;}
【加入我们,让孩子在信息学赛道上跑得更快、更稳!欢迎关注留言!】
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第30张
2026 年 3 月 GESP C++ 四级真题解析(多级指针、数组指针、结构体参数传递、输出重定向) 第31张

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