

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

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
第 2 题 执行下面代码后,输出为( )。

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 之前)
用内存地址 + 存储内容的方式,直观展示变量、指针的关系:
a | int | 0x100 | 5 | |
p | int* | 0x200 | 0x100 | a的地址 |
q | int** | 0x300 | 0x200 | p的地址 |
二、执行 **q +=7 后的状态
**q 最终指向 a,因此 **q +=7 等价于 a = a +7:a | int | 0x100 | 12 | |
p | int* | 0x200 | 0x100 | a |
q | int** | 0x300 | 0x200 | p |
1、int a = 5;0x100,存入 52、int* p = &a;0x200,存入 a 的地址 0x1003、int** q = &p;0x300,存入 p 的地址 0x2004、**q += 7;*q 解引用 q,得到 p(地址0x200的内容0x100)**q 解引用 *q,得到 a(地址0x100的内容5)**q +=7 → a = 5+7=12,修改0x100的内容为125、cout << a << " " << *p;a 是12,*p 解引用p,得到a的值1212 121、* 是解引用运算符:*指针 = 指针指向的变量
*p = a
*q = p
**q = *(*q) = *p = a
2、指针本身的地址永远不变:p永远存a的地址,q永远存p的地址,修改的是a的值
3、多级指针的本质:层层指向,最终指向原变量,修改最内层就是修改原变量
第 3 题 已知:

则表达式 *(*(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]
第 4 题 执行下面程序后,输出为( )。

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
参数传递:值传递不改实参,引用 / 指针传递改实参
第 5 题 执行下面程序后输出为( )。

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强制访问全局变量,不受局部变量屏蔽第 6 题 下列关于结构体初始化的写法,正确的是( )。


【答案】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 都不对
第 7 题 执行下面代码后输出为( )。

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
结构体传参:值传递不改原结构体,引用传递改原结构体
第 8 题 关于递推算法的描述,正确的是( )。
A. 递推表现为函数自己调用自己
B. 递推从已知初值出发,利用递推关系逐步推出后续结果
C. 递推只能用于指数复杂度问题
D. 递推一定需要回溯
【答案】B
【考纲知识点】递推算法的定义与特点
1、递推:从已知的初始值出发,通过固定的递推关系(公式),一步步计算出后续所有结果,是迭代式的,无函数自调用
2、递归:函数自己调用自己,是递推的逆过程(从问题到子问题)
3、递推的时间复杂度通常是线性的(如斐波那契数列递推 O(n)),不是指数级
4、递推是正向计算,不需要回溯(回溯是递归 / 深度优先搜索的特性)
【解析】
A 选项:❌ 错误。函数自调用是递归,不是递推
B 选项:✅ 正确。递推的核心就是「从初值出发,用递推关系逐步计算」
C 选项:❌ 错误。递推通常用于线性复杂度问题,不是指数级
D 选项:❌ 错误。递推是正向迭代,不需要回溯
递推 vs 递归:递推是「从初值往后算」,递归是「函数自己调自己」
第 9 题 执行 climb(6) 的返回值为( )。

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 的相对顺序保持为 A 在 B 前、B 在 D 前
C. 不稳定,因为 C 跑到前面了
D. 无法判断
【答案】B
【考纲知识点】排序算法的稳定性定义
稳定排序:相等元素的相对顺序在排序前后保持不变
本题中,score=90 的元素初始顺序:A → B → D
排序后顺序:A → B → D,相对顺序完全不变
C 分数不同,位置变化不影响稳定性
【解析】
A 选项:❌ 错误。相同分数本身不代表不稳定,关键是相对顺序是否改变
B 选项:✅ 正确。相同score的相对顺序完全保留,符合稳定排序定义
C 选项:❌ 错误。C分数不同,位置变化不影响稳定性
D 选项:❌ 错误。可以明确判断为稳定
第 11 题 下面代码试图把数组按升序进行“插入排序”,横线处应填写( )。

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 题 下列代码段的时间复杂度为( )。


【答案】C
【考纲知识点】时间复杂度分析
【解析】
双层嵌套循环,外层循环n次,内层循环n次
总执行次数:n × n = n²,if判断不影响时间复杂度量级
时间复杂度为O(n²)
时间复杂度:双层循环O(n²),单层O(n)
第 13 题 下面哪种方式不能实现将字符串 Welcome to 2026! 输出重定向到文件 log.txt ( )。


【答案】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 题 执行下面程序,输出结果是( )。

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 循环的条件应为( )。

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

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

【答案】√
【考纲知识点】引用传递(Reference)
【解析】
int &x 是引用,它是变量 a 的别名。
在函数 add10 中对 x 的修改,直接作用于实参 a。
计算过程
初始: a = 5调用 add10(a):x是a的别名,x += 10→a = 5 + 10 = 15最终 a = 15
第 2 题 引用一旦绑定某个变量,就不能再绑定其他变量。( )
【答案】√
【考纲知识点】引用的特性
1、引用在定义初始化时必须绑定一个变量。
2、一旦绑定成功,终身绑定,无法再改变它的指向(即不能重新绑定另一个变量)。
3、赋值运算符 = 改变的是变量的值,不是引用的绑定关系。
第 3 题 执行下面代码,输出结果为 5 。

【答案】×
【考纲知识点】二维数组的内存布局与指针运算
a[2][3] 在内存中是连续存放的,顺序如下:a[0][0] | |
a[0][1] | |
a[0][2] | |
a[1][0] | |
a[1][1] | |
a[1][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、结果是 4,不是 5。
第 4 题 下面程序可以正常编译并输出 10 。

【答案】×
【考纲知识点】函数重载与默认参数的二义性
C++ 中函数重载要求参数列表(类型、数量、顺序)不同
【解析】
本题中存在两个 calc 函数,一个单参数,一个双参数。
调用 calc(5) 时:
候选 1:calc(int x) -> 完美匹配,参数 5 -> 返回 10。
候选 2:calc(int x, int y = 10) -> 也可以匹配,使用默认参数 y=10,返回 50。
编译器在匹配时发现有两个完全匹配的重载版本,会报 “对重载函数的调用不明确”*的编译错误,无法通过编译。
第 5 题 下面程序执行后输出 2010 。

【答案】√
【考纲知识点】作用域与函数调用语法
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,与题目描述完全一致。
第 6 题 在 C++ 中,如果声明了一个指针变量但没有显式初始化,该指针会自动被初始化为 nullptr 。
【答案】×
【考纲知识点】指针的初始化规则
static):会自动初始化为 nullptr。【解析】题目未说明作用域,默认按局部指针处理,因此不会自动初始化为 nullptr。
第 7 题 下面代码没有语法错误。

【答案】√
【考纲知识点】C++ 嵌套结构体定义语法
C++ 允许在结构体内部定义嵌套结构体(成员结构体),并直接声明该类型的成员变量。
【解析】
嵌套 struct Equipment,并声明成员equipment嵌套 struct Skill,并声明数组成员skills[8]所有成员定义符合语法,无语法错误
第 8 题 下面程序能够把 Hello 写入 data.txt 文件中。

【答案】×
【考纲知识点】C++ 文件输出流与标准输出的区别
ofstream fout是文件输出流,用于向文件写入数据
cout是标准输出流,默认输出到控制台(屏幕)
【解析】代码中仅创建了文件流,但没有用fout输出内容,cout的输出仍在控制台,不会写入文件
第 9 题 由于选择排序和插入排序的时间复杂度均为O(n²),在任何实际场景下两者的性能表现几乎相同,可以互相替代。
【答案】×
【考纲知识点】选择排序 vs 插入排序的性能差异
【解析】
时间复杂度:两者最坏 / 平均时间复杂度均为O(n²),但实际性能差异极大
1、插入排序:最好情况(已排序数组)时间复杂度为O(n),且仅在需要移动时赋值,缓存友好,实际运行速度远快于选择排序
2、选择排序:无论数组是否有序,都需要完整遍历,交换次数固定,性能远差于插入排序
实际场景:插入排序在小规模、部分有序数据中性能远优于选择排序,不可互相替代
第 10 题 下面用递推方式计算斐波那契数列第 n 项的程序,时间复杂度是
O(2ⁿ)。

【答案】×
【考纲知识点】递推 vs 递归的时间复杂度
【解析】
1、本题是递推(迭代)实现:单层for循环,循环次数为n−1次,时间复杂度为O(n)
3.1 编程题 1
试题名称:山之谷
时间限制:1.0 s
内存限制:512.0 MB
3.1.1 题目描述


方法一:方向数组版(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, -1, 0, 0, 1, 1, 1};int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};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;}
【考纲知识点】
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、列数mint n, m;// 定义海拔数组h,大小105×105(满足数据范围n,m≤100的要求,留足边界空间)int h[105][105];// 读取网格大小n、mcin >> 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,初始为0int 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,说明当前单元格是山谷,计数器+1ans += 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 题目描述
商店推出了许多礼盒,每个礼盒中包含 k 件商品,每件商品都有一个价格。
现在需要对这些礼盒进行排序,排序规则如下:
1. 先按礼盒总价格从小到大排序;
2. 如果总价格相同,按礼盒中最贵商品的价格从小到大排序;
3. 如果仍然相同,按礼盒中最便宜商品的价格从小到大排序;
4. 如果仍然相同,按礼盒编号从小到大排序。
请输出排序后的礼盒编号。
3.2.2 输入格式
第一行包含两个整数 n和k ,分别表示礼盒数量和每个礼盒中商品的数量。
接下来n 行,每行包含 k个整数,第i 行表示第 i个礼盒中各商品的价格
3.2.3 输出格式
输出一行,包含排序后的礼盒编号(编号从 开始),用空格分隔。
3.2.4 样例

【考纲知识点】
1. 结构体(struct)封装数据
(1)作用:将每个礼盒的总价、最高价、最低价、编号这 4 个关联数据,打包成一个 Combo结构体,方便统一管理、排序。(2)优势:避免用多个独立数组存储数据,代码更简洁、逻辑更清晰,排序时直接操作结构体对象即可。
2. 自定义排序规则(sort + 比较函数)
(1)C++ 的 std::sort函数默认按升序排序基本类型(如 int),对于自定义结构体,需要手动提供比较函数cmp,定义排序规则。(2)比较函数规则: cmp(a,b)返回true时,表示a应该排在b的前面。(3)本题的 4 级优先级规则: 总价 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和kcin >> n >> k;// 定义vector容器v,存储n个Combo类型的礼盒对象vector<Combo> v(n);// 遍历每个礼盒(i从0到n-1,对应第1到第n个礼盒)for (int i = 0; i < n; i++) {// 初始化当前礼盒的总价为0v[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;// 读取当前商品的价格xcin >> 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.sum) return 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;}

