2025年GESP9月认证C++四级真题解析

在这里插入图片描述

一、单选题(每题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
解析
二维数组的地址逻辑:

  1. arr是二维数组首地址,指向第一行({1,2,3});
  2. arr + 1指向第二行({4,5,6}),*(arr + 1)是第二行的首地址(等价于arr[1]);
  3. *(arr + 1) + 2指向第二行的第三个元素(arr[1][2]);
  4. 最终*(*(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. 条件1:当nums[j] < nums[minIndex]时,说明j指向的元素更小,更新minIndexj
  2. 条件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] > keyarr[j + 1] = key
B. arr[j] < keyarr[j + 1] = key
C. arr[j] > keyarr[j] = key
D. arr[j] < keyarr[j] = key
答案:A
解析
插入排序的核心逻辑是“将待插入元素key插入已排序部分的正确位置”:

  1. 条件1:j >= 0保证不越界,arr[j] > key表示已排序部分的元素比key大,需要后移(arr[j+1] = arr[j]),直到找到arr[j] <= keyj < 0
  2. 条件2:循环结束后,j+1key的插入位置,因此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阶的方法数)。
正确步骤:

  1. 计算当前阶的方法数:current = prev1 + prev2prev1是f(n-1),prev2是f(n-2));
  2. 更新prev2为新的f(n-2)(即原来的prev1);
  3. 更新prev1为新的f(n-1)(即当前的current);
    选项B(或排版修正后的正确选项)符合此逻辑。

题目14

题干:找出数组scores中所有满足scores[i] + scores[j] + scores[k] == 300i < 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+1x未定义(局部变量默认无初始值,值随机),因此结果不是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:排兵布阵

题目描述

地图为nm列网格,1表示适合排兵,0表示不适合。需选择一个不包含0的矩形区域,求该矩形的最大面积(网格数)。

输入格式
  1. 第一行:两个正整数n(行数)和m(列数);
  2. 接下来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;
}
解析
  1. 核心思路:枚举所有可能的矩形上下边界(ud),再对每列检查是否全为1,进而确定左右边界(lr),计算面积并更新最大值。
  2. 时间复杂度:(O(n^2 \times m \times n) = O(n^3 m)),因nm最大为12,效率足够。
  3. 样例验证:若输入为3 3,网格为[[1,1,0],[1,1,1],[1,1,1]],最大矩形为2行2列(面积4),程序输出4。

编程题2:最长连续段

题目描述

数组可任意重排,“连续段”定义为“每个元素比前一个大1”的数组(如[0,1,2])。重排后,求所有是连续段的子数组的最大长度(本质是找重排后最长的连续递增序列,公差为1)。

输入格式
  1. 第一行:正整数n(数组长度);
  2. 第二行: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;
}
解析
  1. 核心思路
    • 排序数组:将重复元素集中,便于按顺序检查连续序列;
    • 遍历统计:记录当前连续序列长度(cnt),遇到a[i] = last + 1cnt++,否则重置cnt=1,最终取cnt的最大值。
  2. 时间复杂度:(O(n \log n))(排序占主导,遍历为(O(n))),可处理(n \leq 10^5)的大数据量。
  3. 样例验证:输入9 9 9 8 2 4 4 3 5 3n=9),排序后为[2,3,3,4,4,5,8,9,9],最长连续序列为[2,3,4,5](长度4),程序输出4。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信奥源老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值