
一、单选题(每题2分,共30分)
题目1
题干:运行下面程序后变量a的值是( )。
int a = 42;
int* p = &a;
*p = *p + 1;
选项:A. 42 B. 43 C. 编译错误 D. 不确定
答案:B
解析:
int* p = &a表示指针p指向变量a的地址,*p是指针的“解引用”操作,代表p指向的变量(即a)。- 语句
*p = *p + 1等价于a = a + 1,因此a的值从42变为43。
题目2
题干:以下关于数组的描述中,( )是错误的。
选项:
A. 数组名是一个指针常量
B. 随机访问数组的元素方便快捷
C. 数组可以像指针一样进行自增操作
D. sizeof(arr)返回的是整个数组arr占用的字节数
答案:C
解析:
- 数组名本质是“指向数组首元素的指针常量”(常量不可修改),因此不能像普通指针那样自增(如
arr++会编译错误),选项C错误。 - 选项A正确(数组名是指针常量);选项B正确(数组通过索引
arr[i]可直接访问元素,时间复杂度O(1));选项D正确(sizeof(arr)计算整个数组的字节数,而非指针大小)。
题目3
题干:给定如下定义的数组arr,则*(*(arr + 1) + 2)的值是( )。
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
选项:A. 2 B. 5 C. 4 D. 6
答案:D
解析:
二维数组的地址逻辑:
arr是二维数组首地址,指向第一行({1,2,3});arr + 1指向第二行({4,5,6}),*(arr + 1)是第二行的首地址(等价于arr[1]);*(arr + 1) + 2指向第二行的第三个元素(arr[1][2]);- 最终
*(*(arr + 1) + 2)等价于arr[1][2],值为6。
题目4
题干:下面这段代码会输出( )。
int add(int a, int b = 1); // 函数声明
int main() {
cout << add(2) << " " << add(2, 3);
return 0;
}
int add(int a, int b) { // 函数定义
return a + b;
}
选项:A. 3 5 B. 编译失败:定义处少了默认参数 C. 运行错误 D. 链接失败:未定义引用
答案:A
解析:
C++中默认参数的规则是“仅在声明或定义中出现一次”。本题中默认参数b=1在声明中指定,定义时无需重复,函数可正常调用:
add(2)使用默认参数b=1,结果为2+1=3;add(2,3)显式传参b=3,结果为2+3=5;
最终输出“3 5”。
题目5
题干:下面这段代码会输出( )。
int x = 5; // 全局变量x
void foo() {
int x = 10; // 局部变量x(屏蔽全局x)
cout << x << " ";
}
void bar() {
cout << x << " "; // 无局部x,使用全局x
}
int main() {
foo(); bar();
}
选项:A. 5 5 B. 10 10 C. 5 10 D. 10 5
答案:D
解析:
变量的“作用域屏蔽”规则:局部变量会屏蔽同名全局变量:
foo()中定义了局部x=10,输出10;bar()中无局部x,使用全局x=5,输出5;
最终输出“10 5”。
题目6
题干:下面程序运行的结果是( )。
void increaseA(int x) { x++; } // 值传递,不修改实参
void increaseB(int* p) { (*p)++; } // 指针传递,修改实参
int main() {
int a = 5;
increaseA(a);
cout << a << " ";
increaseB(&a);
cout << a;
}
选项:A. 6 7 B. 6 6 C. 5 6 D. 5 5
答案:C
解析:
increaseA(int x)是值传递:函数内修改的是参数x的副本,实参a仍为5,因此第一个输出为5;increaseB(int* p)是指针传递:(*p)解引用指向实参a,(*p)++等价于a++,a变为6,第二个输出为6;
最终输出“5 6”。
题目7
题干:关于结构体初始化,以下哪个选项中正确的是( )。
struct Point { int x, y; };
选项:A. Point p = (1,2); B. Point p = {1,2}; C. Point p = new {1,2}; D. Point p = <1,2>;
答案:B
解析:
C++结构体(聚合类型)的正确初始化方式是大括号{}聚合初始化:
- 选项A用小括号
(),错误(小括号用于构造函数初始化,此处无自定义构造函数); - 选项C用
new,错误(new返回指针,应写Point* p = new Point{1,2};); - 选项D用尖括号
<>,错误(无此初始化语法);
选项B符合聚合初始化规则,正确。
题目8
题干:运行如下代码会输出( )。
struct Cat {
string name;
int age;
};
void birthday(Cat& c) { c.age++; } // 引用传递,修改原对象
int main() {
Cat kitty{"Mimi", 2};
birthday(kitty);
cout << kitty.name << " " << kitty.age;
}
选项:A. Mimi 2 B. Mimi 3 C. kitty 3 D. kitty 2
答案:B
解析:
birthday(Cat& c)的参数是引用&,函数内修改c.age会直接作用于原对象kitty;kitty初始年龄为2,调用birthday后年龄变为3,名字仍为“Mimi”;
最终输出“Mimi 3”。
题目9
题干:关于排序算法的稳定性,以下说法错误的是( )。
选项:
A. 稳定的排序算法不改变相等元素的相对位置
B. 冒泡排序是稳定的排序算法
C. 选择排序是稳定的排序算法
D. 插入排序是稳定的排序算法
答案:C
解析:
排序稳定性的核心是“相等元素的相对位置是否保持”:
- 选项A是稳定性的定义,正确;
- 冒泡排序(相邻交换,相等不交换)、插入排序(相等元素不后移)均稳定,选项B、D正确;
- 选择排序不稳定:例如数组
[2, 2, 1],第一次选择最小元素1与第一个2交换,得到[1, 2, 2],两个2的相对位置改变,选项C错误。
题目10
题干:下面代码试图实现选择排序(升序),则横线上应分别填写( )。
void selectionSort(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n - 1; ++i) {
int minIndex = i; // 初始假设i是最小值索引
for (int j = i + 1; j < n; ++j) {
if (__________) { // 条件1:找到更小的元素
minIndex = j;
}
}
__________; // 条件2:交换i和minIndex的元素
}
}
选项:
A. nums[j] < nums[minIndex];swap(nums[i], nums[minIndex])
B. nums[j] > nums[minIndex];swap(nums[i], nums[minIndex])
C. nums[j] <= nums[minIndex];swap(nums[j], nums[minIndex])
D. nums[j] <= nums[minIndex];swap(nums[i], nums[j])
答案:A
解析:
选择排序的核心逻辑是“每轮找未排序部分的最小值,与未排序部分的第一个元素交换”:
- 条件1:当
nums[j] < nums[minIndex]时,说明j指向的元素更小,更新minIndex为j; - 条件2:遍历结束后,将最小值(
minIndex指向)与未排序部分的第一个元素(i)交换,即swap(nums[i], nums[minIndex])。
题目11
题干:下面程序实现插入排序(升序),则横线上应分别填写( )。
void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i]; // 待插入的元素
int j = i - 1; // 已排序部分的最后一个索引
// 条件1:找到key的插入位置,同时后移元素
while (j >= 0 && __________) {
arr[j + 1] = arr[j]; // 元素后移
j--;
}
__________; // 条件2:将key插入正确位置
}
}
选项:
A. arr[j] > key;arr[j + 1] = key
B. arr[j] < key;arr[j + 1] = key
C. arr[j] > key;arr[j] = key
D. arr[j] < key;arr[j] = key
答案:A
解析:
插入排序的核心逻辑是“将待插入元素key插入已排序部分的正确位置”:
- 条件1:
j >= 0保证不越界,arr[j] > key表示已排序部分的元素比key大,需要后移(arr[j+1] = arr[j]),直到找到arr[j] <= key或j < 0; - 条件2:循环结束后,
j+1是key的插入位置,因此arr[j+1] = key。
题目12
题干:关于插入排序的时间复杂度,下列说法正确的是( )。
选项:
A. 最好情况和最坏情况的时间复杂度都是 (O(n^2))
B. 最好情况是 (O(n)),最坏情况是 (O(n^2))
C. 最好情况是 (O(n)),最坏情况是 (O(2^n))
D. 最好情况是 (O(n^2)),最坏情况是 (O(2^n))
答案:B
解析:
插入排序的时间复杂度取决于数组的初始顺序:
- 最好情况:数组已升序排列,每个元素仅需与前一个元素比较1次,无需移动,时间复杂度 (O(n));
- 最坏情况:数组逆序排列,每个元素需与已排序部分的所有元素比较并移动,时间复杂度 (O(n^2))。
题目13
题干:小杨爬楼梯,需n阶到达楼顶,每次爬1或2阶,求不同方法数。下面代码中横线上应填写( )。
int climbStairs(int n) {
if (n <= 2) return n; // 初始条件:n=1→1种,n=2→2种
int prev2 = 1; // 对应f(n-2)
int prev1 = 2; // 对应f(n-1)
int current = 0;
for (int i = 3; i <= n; ++i) {
__________; // 计算f(n)并更新prev2、prev1
}
return current;
}
选项:
A. prev1 = current; prev2 = prev1; current = prev1 + prev2;
B. current = prev1 + prev2; prev1 = current; prev2 = prev1;
C. current = prev1 + prev2; prev2 = prev1; prev1 = current;
D. prev2 = prev1; prev1 = current; current = prev1 + prev2;
答案:B(注:题目选项格式可能存在排版误差,核心逻辑为“先算current,再更新prev1和prev2”)
解析:
爬楼梯问题本质是斐波那契数列:f(n) = f(n-1) + f(n-2)(第n阶的方法数=第n-1阶+第n-2阶的方法数)。
正确步骤:
- 计算当前阶的方法数:
current = prev1 + prev2(prev1是f(n-1),prev2是f(n-2)); - 更新
prev2为新的f(n-2)(即原来的prev1); - 更新
prev1为新的f(n-1)(即当前的current);
选项B(或排版修正后的正确选项)符合此逻辑。
题目14
题干:找出数组scores中所有满足scores[i] + scores[j] + scores[k] == 300(i < j < k)的三元组,代码用三重循环实现,其时间复杂度是( )。
int cnt = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
for (int k = j + 1; k < n; k++) {
if (scores[i] + scores[j] + scores[k] == 300) cnt++;
}
}
}
选项:A. (O(n)) B. (O(n^2)) C. (O(n^3)) D. (O(2^n))
答案:C
解析:
时间复杂度取决于循环嵌套层数:
- 外层循环
i:执行次数约n; - 中层循环
j:每次执行次数约n; - 内层循环
k:每次执行次数约n;
总执行次数与n^3成正比,因此时间复杂度为 (O(n^3))。
题目15
题干:关于异常处理,以下说法错误的是( )。
选项:
A. try块中的代码可能会抛出异常
B. catch块可以有多个,处理不同类型的异常
C. throw语句用于抛出异常
D. 所有异常都必须被捕获,否则程序会崩溃
答案:D
解析:
C++异常处理规则:
- 选项A正确(
try块是“可能抛出异常的代码块”); - 选项B正确(多个
catch块可按类型匹配不同异常,如catch(int)、catch(string)); - 选项C正确(
throw用于主动抛出异常,如throw 42;); - 选项D错误:未捕获的异常会导致程序终止(崩溃),但并非“所有异常都必须被捕获”(语法允许不捕获,只是运行时会出错)。

二、判断题(每题2分,共20分)
| 题号 | 题干描述 | 答案 | 详细解析 |
|---|---|---|---|
| 1 | 代码int a = 5; int *p = a;能正确初始化指针。 | × | 指针初始化需指向变量的地址,应写int *p = &a;。直接赋值a(int类型)给p(int*类型)会导致类型不匹配,编译错误。 |
| 2 | 代码int x=10; void f(){int x=x+1; cout<<x;}执行后输出11。 | × | 函数f()中的x是局部变量,初始化时x=x+1的x未定义(局部变量默认无初始值,值随机),因此结果不是11。 |
| 3 | 代码struct Student{string name; int age; float score;}; Student* students = new Student[20];合法。 | √ | new Student[20]会动态分配20个Student对象的数组,string成员会调用默认构造函数初始化,语法合法。 |
| 4 | 代码void func(int*p){*p=10;} main(){int a=5; func(&a); cout<<a;}执行后输出10。 | √ | func通过指针p解引用修改实参a的值(*p=10等价于a=10),因此输出10。 |
| 5 | 二维数组arr[3][4]传递给函数f,函数参数声明为int arr[][4]是错误的。 | × | 二维数组传参时,第一维可省略,第二维必须指定(编译器需知道每行的元素数)。int arr[][4]是合法声明,函数内可通过arr[i][j]访问元素。 |
| 6 | 递推是在给定初始条件下,已知前一项(或前几项)求后一项的过程。 | √ | 递推的核心定义:通过初始条件和递推公式,由前项推导后项(如斐波那契数列)。 |
| 7 | 插入排序时间复杂度为(O(n^2)),但单元操作少,小数据量排序中受欢迎。 | √ | 插入排序的比较、移动操作(单元操作)比冒泡排序少,因此在小数据量场景下效率高于其他(O(n^2))算法(如选择排序)。 |
| 8 | 对数组{4,1,3,1,5,2}进行冒泡排序(一轮,将最大元素放最后),结果为{4,1,3,1,2,5}。 | × | 冒泡排序一轮的正确过程:相邻元素比较交换,最大元素5逐步后移,最终结果为{1,3,1,4,2,5},而非题干所述。 |
| 9 | 代码try{throw 42;} catch(...){cout<<"Caught";}只能捕获int类型异常。 | × | catch(...)是“万能捕获”,可捕获所有类型的异常(包括int、string、自定义异常等),并非仅int类型。 |
| 10 | 代码ofstream file("data.txt"); cout<<"Hello"; file.close();将Hello写入文件data.txt。 | × | cout用于输出到控制台,写入文件需用文件流对象file,即file<<"Hello";。题干代码未向文件写入内容,错误。 |
三、编程题(每题25分,共50分)
编程题1:排兵布阵
题目描述
地图为n行m列网格,1表示适合排兵,0表示不适合。需选择一个不包含0的矩形区域,求该矩形的最大面积(网格数)。
输入格式
- 第一行:两个正整数
n(行数)和m(列数); - 接下来
n行:每行m个整数(0或1),表示网格状态。
输出格式
一行:一个整数,表示最大矩形的面积。
参考程序
#include <algorithm>
#include <cstdio>
using namespace std;
const int N = 15;
int n, m;
int a[N][N]; // 1-based索引存储网格
int ans = 0; // 最大面积
int main() {
scanf("%d%d", &n, &m);
// 读入网格(1-based,方便边界处理)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
}
}
// 枚举所有可能的上下边界(u:上边界行,d:下边界行)
for (int u = 1; u <= n; u++) {
for (int d = u; d <= n; d++) {
int chk = 1; // 标记当前列是否全为1(u到d行)
int l = 1; // 左边界列
// 枚举右边界列r
for (int r = 1; r <= m; r++) {
// 检查当前列r在u到d行是否全为1
for (int x = u; x <= d; x++) {
chk &= a[x][r]; // 有一个0则chk变为0
}
if (!chk) { // 当前列包含0,左边界移到r+1,重置chk
l = r + 1;
chk = 1;
} else { // 计算当前矩形面积,更新最大值
int area = (r - l + 1) * (d - u + 1);
ans = max(ans, area);
}
}
}
}
printf("%d\n", ans);
return 0;
}
解析
- 核心思路:枚举所有可能的矩形上下边界(
u到d),再对每列检查是否全为1,进而确定左右边界(l到r),计算面积并更新最大值。 - 时间复杂度:(O(n^2 \times m \times n) = O(n^3 m)),因
n和m最大为12,效率足够。 - 样例验证:若输入为
3 3,网格为[[1,1,0],[1,1,1],[1,1,1]],最大矩形为2行2列(面积4),程序输出4。
编程题2:最长连续段
题目描述
数组可任意重排,“连续段”定义为“每个元素比前一个大1”的数组(如[0,1,2])。重排后,求所有是连续段的子数组的最大长度(本质是找重排后最长的连续递增序列,公差为1)。
输入格式
- 第一行:正整数
n(数组长度); - 第二行:
n个整数,表示数组元素(可重复,范围(-10^9 \leq a_i \leq 10^9))。
输出格式
一行:一个整数,表示最长连续段的长度。
参考程序
#include <algorithm>
#include <cstdio>
using namespace std;
const int N = 1e5 + 5;
int n;
int a[N]; // 存储数组
int last; // 上一个元素
int cnt; // 当前连续序列长度
int mx; // 最大连续序列长度
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
sort(a + 1, a + n + 1); // 排序数组,便于找连续序列
last = a[1];
cnt = 1;
mx = 1;
// 遍历排序后的数组,统计最长连续序列
for (int i = 2; i <= n; i++) {
if (a[i] == last) { // 重复元素,跳过(不影响连续序列)
continue;
}
if (a[i] == last + 1) { // 当前元素是上一个+1,连续序列长度+1
cnt++;
mx = max(mx, cnt);
} else { // 不连续,重置当前序列长度
cnt = 1;
}
last = a[i]; // 更新上一个元素
}
printf("%d\n", mx);
return 0;
}
解析
- 核心思路:
- 排序数组:将重复元素集中,便于按顺序检查连续序列;
- 遍历统计:记录当前连续序列长度(
cnt),遇到a[i] = last + 1则cnt++,否则重置cnt=1,最终取cnt的最大值。
- 时间复杂度:(O(n \log n))(排序占主导,遍历为(O(n))),可处理(n \leq 10^5)的大数据量。
- 样例验证:输入
9 9 9 8 2 4 4 3 5 3(n=9),排序后为[2,3,3,4,4,5,8,9,9],最长连续序列为[2,3,4,5](长度4),程序输出4。
3593

被折叠的 条评论
为什么被折叠?



