GESP C++五级 2026年3月真题详细解析

四季读书网 4 0
GESP C++五级 2026年3月真题详细解析
GESP C++五级 2026年3月真题详细解析 第1张

💪艾墨舟编程小先锋专为GESP考生打造的免费题库小程序。涵盖图形化/Python/C++全真题,支持真题模考、考前预测、错题整理与学习统计,一站式攻克客观题。


📋 内容概览

本文包含2026年3月GESP C++五级考试15道单选题的解析,精准匹配考纲知识点,附解题技巧与易错点提示。

🎯 试题解析

───── ✨ 第1题 ✨ ─────

📖 题目

关于单链表、双链表和循环链表,下列说法正确的是( )。A. 在单链表中,若已知任意结点的指针,则可以在O(1)时间内删除该结点。B. 循环链表中一定不存在空指针。C. 在循环双链表中,尾结点的next指针一定为nullptr。D. 在带头结点的循环单链表中,判定链表是否为空只需判断头结点的next是否指向自身。

📌 大纲对应知识点:线性表(链表)🎯 考查目标:掌握各类链表的结构特性和操作逻辑

📋 选项详解

选项
是否正确
详细解析
A. 在单链表中,若已知任意结点的指针,则可以在O(1)时间内删除该结点。
单链表删除结点需要找到前驱结点,最坏需要O(n)时间,无法直接删除。
B. 循环链表中一定不存在空指针。
不带头结点的空循环链表可能存在空指针,表述过于绝对。
C. 在循环双链表中,尾结点的next指针一定为nullptr。
循环链表尾结点next指向头结点,而非空指针。
D. 在带头结点的循环单链表中,判定链表是否为空只需判断头结点的next是否指向自身。
✅ 正确选项
带头结点的循环单链表为空时,头结点next指向自己,判断逻辑正确。

⭐ 答案:D📌 知识点:链表结构特性

💡 解题小贴士:循环链表的核心特征是尾结点与头结点相连,带头结点的空表头结点的next必然指向自身。

───── ✨ 第2题 ✨ ─────

📖 题目

双向循环链表中要在结点p之前插入新结点s(均非空),以下指针操作正确的是( )。A.s->next = p;p->prev = s;p->next = s;s->prev = p;B.s->prev = p;s->next = p->next;p->next->prev = s;p->next = s;C.s->next = p;s->prev = p->prev;p->prev->next = s;p->prev = s;D.s->next = p;s->prev = nullptr;p->prev = s;

📌 大纲对应知识点:双链表的插入操作🎯 考查目标:掌握双链表插入时的指针修改顺序

📋 选项详解

选项
是否正确
详细解析
A
会修改p的next指向s,丢失p原来的后继结点,逻辑错误。
B
该操作是将s插入到p的后面,不符合在p之前插入的要求。
C
✅ 正确选项
先关联s的前驱后继,再修改p前驱的next和p的prev,逻辑正确。
D
s的prev设为nullptr不符合循环链表结构,且未修改原前驱的next指针。

⭐ 答案:C📌 知识点:双链表插入操作

💡 解题小贴士:双链表插入结点口诀:先连新结点两端,再改旧结点的前后指针,避免断链。

───── ✨ 第3题 ✨ ─────

📖 题目

下面函数用“哑结点”统一处理删除单向链表中的头结点与中间结点。横线处应填( )。

struct Node{    int val;    Node* next;    Node(int v):val(v),next(nullptr){}};Node* eraseAll(Node* head, int x){    Node dummy(0);    dummy.next = head;    Node* cur = &dummy;    while(cur->next){        if(cur->next->val == x){            Node* del = cur->next;            ______________________            delete del;        }else cur = cur->next;    }    return dummy.next;}

A. cur = cur->next;B. cur->next = del->next;C. del->next = cur->next;D. cur->next = nullptr;

📌 大纲对应知识点:单链表的删除操作🎯 考查目标:掌握哑结点法删除链表结点的核心逻辑

📋 选项详解

选项
是否正确
详细解析
A. cur = cur->next;
仅移动指针,未删除结点,会导致内存泄漏和逻辑错误。
B. cur->next = del->next;
✅ 正确选项
将当前结点的next指向待删结点的后继,完成结点摘除,逻辑正确。
C. del->next = cur->next;
修改待删结点的指针,没有实际摘除结点,链表未改变。
D. cur->next = nullptr;
直接断开链表,会丢失后续所有结点,逻辑错误。

⭐ 答案:B📌 知识点:单链表删除操作

💡 解题小贴士:删除链表结点的核心是让待删结点的前驱直接指向待删结点的后继,跳过待删结点。

───── ✨ 第4题 ✨ ─────

📖 题目

对如下代码实现的欧几里得算法(辗转相除法),执行gcd(48, 18)得到的调用序列为( )。

int gcd(int a, int b) {    return b == 0 ? a : gcd(b, a % b);}

A. gcd(48,18) -> gcd(18,12) -> gcd(12,6) -> gcd(6,0)B. gcd(48,18) -> gcd(30,18) -> gcd(12,18)C. gcd(48,18) -> gcd(18,30) -> gcd(30,6)D. gcd(48,18) -> gcd(12,18) -> gcd(6,12)

📌 大纲对应知识点:递归函数、数论(最大公约数)🎯 考查目标:掌握辗转相除法的执行流程

📋 选项详解

选项
是否正确
详细解析
A. gcd(48,18) -> gcd(18,12) -> gcd(12,6) -> gcd(6,0)
✅ 正确选项
每次调用将b作为新的a,a%b作为新的b,直到b为0,计算过程正确。
B. gcd(48,18) -> gcd(30,18) -> gcd(12,18)
辗转相除法用的是取模而非减法,逻辑错误。
C. gcd(48,18) -> gcd(18,30) -> gcd(30,6)
a%b结果为12而非30,参数传递错误。
D. gcd(48,18) -> gcd(12,18) -> gcd(6,12)
参数顺序错误,新的a应为b,新的b应为a%b。

⭐ 答案:A📌 知识点:递归函数、欧几里得算法

💡 解题小贴士:辗转相除法核心:gcd(a,b) = gcd(b, a mod b),直到b为0时返回a。

───── ✨ 第5题 ✨ ─────

📖 题目

下面代码实现了欧拉(线性)筛,横线处应填写( )。

vector<int> euler_sieve(int n) {    vector<bool> is_composite(n + 1, false);     vector<int>  primes;                         for (int i = 2; i <= n; i++) {        if (!is_composite[i])            primes.push_back(i);        for (int j = 0; __________________________ && (long long)i * primes[j] <= n; j++) {            is_composite[i * primes[j]] = true;            if (i % primes[j] == 0)                break;        }    }    return primes;}

A. j <= nB. j < sqrt(n)C. j < primes.size()D. j < i

📌 大纲对应知识点:数论(素数筛法)🎯 考查目标:掌握欧拉筛的核心实现逻辑

📋 选项详解

选项
是否正确
详细解析
A. j <= n
j是primes数组的下标,n远大于primes数组长度,会越界。
B. j < sqrt(n)
与sqrt(n)无关,无法保证遍历所有已筛出的素数。
C. j < primes.size()
✅ 正确选项
j需要遍历所有已经找到的素数,不超过primes数组长度。
D. j < i
素数数量可能小于i,逻辑不严谨。

⭐ 答案:C📌 知识点:欧拉筛法

💡 解题小贴士:欧拉筛内层循环遍历已存储的素数,当i能整除当前素数时停止,保证每个合数只被最小质因子筛一次。

───── ✨ 第6题 ✨ ─────

📖 题目

埃氏筛中将内层循环从j = ii开始而不是j = 2i的主要原因是( )。

vector<int> eratosthenes_sieve(int n) {    vector<bool> is_composite(n + 1, false);    vector<int>  primes;    for (int i = 2; i <= n; i++) {        if (is_composite[i]) continue;        primes.push_back(i);        for (long long j = (long long)i * i; j <= n; j += i)            is_composite[j] = true;    }    return primes;}

A. 因为2i一定不是合数B. ii一定是质数C. 小于i*i的i的倍数已被更小质因子筛过D. 这样可以把时间复杂度降为O(n)

📌 大纲对应知识点:数论(素数筛法)🎯 考查目标:理解埃氏筛的优化逻辑

📋 选项详解

选项
是否正确
详细解析
A. 因为2*i一定不是合数
2*i显然是合数(i≥2时),表述完全错误。
B. i*i一定是质数
i*i是平方数,除了i=1外都是合数,表述错误。
C. 小于i*i的i的倍数已被更小质因子筛过
✅ 正确选项
如i=5时,10、15、20已被2、3筛过,直接从25开始即可。
D. 这样可以把时间复杂度降为O(n)
埃氏筛时间复杂度是O(n log log n),线性筛才是O(n)。

⭐ 答案:C📌 知识点:埃氏筛法优化

💡 解题小贴士:埃氏筛从i*i开始是为了避免重复标记更小的倍数,减少重复操作,但时间复杂度仍高于线性筛。

───── ✨ 第7题 ✨ ─────

📖 题目

下面程序的运行结果为( )。

bool check(int n, int a[], int k, int dist) {    int cnt = 1;    int last = a[0];    for (int i = 1; i < n; i++) {        if (a[i] - last >= dist) {            cnt++;            last = a[i];        }    }    return cnt >= k;}int solve(int n, int a[], int k) {    std::sort(a, a + n);    int l = 0;    int r = a[n - 1] - a[0];    while (l < r) {        int mid = (l + r + 1) / 2;        if (check(n, a, k, mid))            l = mid;        else            r = mid - 1;    }    return l;}int main() {    int a[] = {1, 2, 8, 4, 9};    int n = 5;    int k = 3;    std::cout << solve(n, a, k) << std::endl;    return 0;}

A. 2B. 3C. 4D. 5

📌 大纲对应知识点:二分答案🎯 考查目标:掌握二分答案的应用场景和执行流程

📋 选项详解

选项
是否正确
详细解析
A. 2
可以找到间距更大的可行解,不是最大值。
B. 3
✅ 正确选项
排序后数组为[1,2,4,8,9],选1、4、8,间距3,满足3个点的要求,是最大可行值。
C. 4
无法找到3个点满足最小间距≥4,不可行。
D. 5
间距5无法选够3个点,不可行。

⭐ 答案:B📌 知识点:二分答案、贪心校验

💡 解题小贴士:最大化最小值类问题通常用二分答案,校验函数用贪心策略尽可能选点,判断是否满足数量要求。

───── ✨ 第8题 ✨ ─────

📖 题目

在升序数组中查找第一个大于等于x的位置,下面循环中横线应填( )。

int lowerBound(const vector<int>& a, int x){    int l=0, r=a.size();     while(l<r){        int mid = l + (r - l)/2;        if(a[mid] >= x)  _____________;        else l = mid + 1;    }    return l;}

A. r = mid;B. r = mid - 1;C. l = mid;D. l = mid + 1;

📌 大纲对应知识点:二分查找🎯 考查目标:掌握lower_bound的实现逻辑

📋 选项详解

选项
是否正确
详细解析
A. r = mid;
✅ 正确选项
当mid位置元素≥x时,第一个≥x的位置在左半区(含mid),所以右边界移到mid。
B. r = mid - 1;
会跳过可能的正确位置(mid本身可能就是第一个≥x的元素)。
C. l = mid;
逻辑错误,会导致死循环。
D. l = mid + 1;
这是a[mid]<x时的操作,不符合当前条件。

⭐ 答案:A📌 知识点:二分查找(lower_bound)

💡 解题小贴士:lower_bound左闭右开区间写法口诀:大于等于往左收(r=mid),小于往右走(l=mid+1)。

───── ✨ 第9题 ✨ ─────

📖 题目

关于递归函数调用,下列说法错误的是( )。A. 递归调用层次过深时,可能会耗尽栈空间导致栈溢出B. 尾递归函数可以通过编译器优化来避免栈溢出C. 所有递归函数都可以通过循环结构来改写,从而避免栈溢出D. 栈溢出发生时,程序会抛出异常并可以继续执行后续代码

📌 大纲对应知识点:递归函数🎯 考查目标:理解递归调用的底层特性和栈溢出问题

📋 选项详解

选项
是否正确
详细解析
A. 递归调用层次过深时,可能会耗尽栈空间导致栈溢出
说法正确,每次递归调用都会在栈中压入栈帧,栈空间有限。
B. 尾递归函数可以通过编译器优化来避免栈溢出
说法正确,尾递归可以复用当前栈帧,不会增加栈深度。
C. 所有递归函数都可以通过循环结构来改写,从而避免栈溢出
说法正确,递归本质是栈操作,都可以用循环+显式栈改写。
D. 栈溢出发生时,程序会抛出异常并可以继续执行后续代码
✅ 正确选项
栈溢出属于严重内存错误,通常会直接导致程序崩溃,无法继续执行。

⭐ 答案:D📌 知识点:递归调用、栈溢出

💡 解题小贴士:栈溢出是未定义行为,程序会直接终止,不会正常抛出可捕获的异常。

───── ✨ 第10题 ✨ ─────

📖 题目

给定n根木头,第i根长度为a[i]。要切成不少于m段等长木段,求最大可能长度,则横线上应填写( )。

const int MAXN = 100005;long long a[MAXN];int n, m;bool check(long long x){    long long cnt = 0;    for(int i = 1; i <= n; i++){        if(x == 0) return true;        cnt += a[i] / x;        if(cnt >= m) return true;    }    return false;}int main(){    cin >> n >> m;    long long mx = 0;    for(int i = 1; i <= n; i++){        cin >> a[i];        mx = max(mx, a[i]);    }    long long l = 1, r = mx;    long long ans = 0;    while(l <= r){        long long mid = l + (r - l) / 2;        if(check(mid)){            ans = mid;            ______________________        }else{            ______________________        }    }    cout << ans << endl;    return 0;}

A. l = mid + 1; r = mid - 1;B. l = mid - 1; r = mid + 1;C. l = mid + 1; r = mid;D. l = mid; r = mid + 1;

📌 大纲对应知识点:二分答案🎯 考查目标:掌握最大化可行值的二分边界调整逻辑

📋 选项详解

选项
是否正确
详细解析
A. l = mid + 1; r = mid - 1;
✅ 正确选项
可行时尝试更大的长度(l右移),不可行时尝试更小的长度(r左移)。
B. l = mid - 1; r = mid + 1;
边界调整逻辑完全相反,会导致死循环和错误结果。
C. l = mid + 1; r = mid;
不可行时r调整为mid错误,应该调整为mid-1。
D. l = mid; r = mid + 1;
边界调整逻辑错误,会导致死循环。

⭐ 答案:A📌 知识点:二分答案

💡 解题小贴士:最大化问题二分口诀:可行则尝试更大(l=mid+1),不可行则缩小(r=mid-1),每次可行时记录答案。

───── ✨ 第11题 ✨ ─────

📖 题目

下面代码用分治求 “最大连续子段和”,其时间复杂度为( )。

int solve(vector<int>& a, int l, int r){    if(l == r) return a[l];    int mid = l + (r - l) / 2;    int left = solve(a, l, mid);    int right = solve(a, mid + 1, r);    int sum = 0, lmax = INT_MIN;    for(int i = mid; i >= l; i--){        sum += a[i];        lmax = max(lmax, sum);    }    sum = 0;    int rmax = INT_MIN;    for(int i = mid + 1; i <= r; i++){        sum += a[i];        rmax = max(rmax, sum);    }    return max({left, right, lmax + rmax});}

A. O(n)B. O(n log n)C. O(n²)D. O(log n)

📌 大纲对应知识点:分治算法、时间复杂度分析🎯 考查目标:掌握分治算法的时间复杂度计算

📋 选项详解

选项
是否正确
详细解析
A. O(n)
每层需要O(n)时间遍历,共有O(log n)层,复杂度高于O(n)。
B. O(n log n)
✅ 正确选项
每次将问题分成两个子问题,每层总遍历时间O(n),共log n层,总复杂度O(n log n)。
C. O(n²)
这是暴力枚举的时间复杂度,分治优于暴力。
D. O(log n)
仅遍历次数的对数,忽略了每层的线性遍历,计算错误。

⭐ 答案:B📌 知识点:分治算法、时间复杂度

💡 解题小贴士:分治时间复杂度用主定理计算,T(n) = 2T(n/2) + O(n),符合主定理情况2,结果为O(n log n)。

───── ✨ 第12题 ✨ ─────

📖 题目

游戏大赛决赛,两组选手分别按得分从小到大排好队,现在要把他们合并成一个有序排行榜。A组:A = {12, 35, 67, 89}, B组:B = {20, 45, 55, 78},下面是归并合并函数的核心循环,横线处应填入( )。

int i = 0, j = 0;vector<int> result;while (i < A.size() && j < B.size()) {    if (___________________) {        result.push_back(A[i++]);    } else {        result.push_back(B[j++]);    }}while (i < A.size()) {    result.push_back(A[i++]);}while (j < B.size()) {    result.push_back(B[j++]);}

A. A[i] >= B[j]B. A[i] <= B[j]C. i >= jD. i <= j

📌 大纲对应知识点:归并排序🎯 考查目标:掌握归并排序中合并两个有序数组的逻辑

📋 选项详解

选项
是否正确
详细解析
A. A[i] >= B[j]
会优先放入B的元素,合并结果为降序,不符合要求。
B. A[i] <= B[j]
✅ 正确选项
当A当前元素小于等于B当前元素时,先放入A的元素,保证升序。
C. i >= j
比较下标没有意义,和元素大小无关。
D. i <= j
比较下标没有意义,逻辑错误。

⭐ 答案:B📌 知识点:归并排序、双指针

💡 解题小贴士:合并两个升序数组口诀:双指针分别指向两个数组头部,每次选较小的元素放入结果,指针后移。

───── ✨ 第13题 ✨ ─────

📖 题目

有n位同学的成绩已经从小到大排好序,现在对它执行下面这段以第一个元素为pivot的快速排序,请问此次排序的时间复杂度是( )。

void quicksort(vector<int>& a, int l, int r) {    if (l >= r) return;    int pivot = a[l];            int i = l, j = r;    while (i < j) {        while (i < j && a[j] >= pivot) j--;        while (i < j && a[i] <= pivot) i++;        if (i < j) swap(a[i], a[j]);    }    swap(a[l], a[i]);    quicksort(a, l, i - 1);    quicksort(a, i + 1, r);}

A. O(n)B. O(n log n)C. O(n²)D. O(log n)

📌 大纲对应知识点:快速排序、时间复杂度分析🎯 考查目标:理解快速排序的最坏时间复杂度场景

📋 选项详解

选项
是否正确
详细解析
A. O(n)
快速排序无论如何都不可能达到线性时间复杂度。
B. O(n log n)
这是快速排序的平均和最好时间复杂度,当前是最坏场景。
C. O(n²)
✅ 正确选项
有序数组选首元素为枢轴,每次划分极不均匀,递归深度为n,总复杂度O(n²)。
D. O(log n)
复杂度计算完全错误。

⭐ 答案:C📌 知识点:快速排序、时间复杂度

💡 解题小贴士:快速排序最坏情况出现在每次划分极度不均(如有序数组选首尾为枢轴),此时退化为冒泡排序,复杂度O(n²)。

───── ✨ 第14题 ✨ ─────

📖 题目

下面关于排序算法的描述中,不正确的是 ( ) 。A. 冒泡排序和插入排序都是稳定的排序算法B. 快速排序和归并排序都是不稳定的排序算法C. 冒泡排序和插入排序最好时间复杂度均为O(n)D. 归并排序在最好、最坏和平均三种情况的时间复杂度均为O(n log n)

📌 大纲对应知识点:排序算法特性🎯 考查目标:掌握常见排序算法的稳定性、时间复杂度特性

📋 选项详解

选项
是否正确
详细解析
A. 冒泡排序和插入排序都是稳定的排序算法
说法正确,两者相等元素不会交换相对位置,是稳定排序。
B. 快速排序和归并排序都是不稳定的排序算法
✅ 正确选项
归并排序是稳定的排序算法,只要合并时相等元素优先取左半区即可。
C. 冒泡排序和插入排序最好时间复杂度均为O(n)
说法正确,数组已有序时,两者都可以O(n)时间完成。
D. 归并排序在最好、最坏和平均三种情况的时间复杂度均为O(n log n)
说法正确,归并排序时间复杂度稳定,不受初始数据影响。

⭐ 答案:B📌 知识点:排序算法特性

💡 解题小贴士:稳定排序口诀:冒(冒泡)插(插入)归(归并)基(基数),其余均为不稳定排序。

───── ✨ 第15题 ✨ ─────

📖 题目

下面代码实现两个整数除法,其中被除数为一个“大整数”,用字符串表示,除数是一个小整数,用int表示,则横线处应该填写( )。

int main(){    string s;    int b;    cin >> s >> b;    vector<int> a;    for(char c : s){        a.push_back(c - '0');    }    vector<int> c;    long long rem = 0;    for(int i = 0; i < a.size(); i++){        rem = rem * 10 + a[i];        int q = rem / b;        c.push_back(q);        ______________________    }    int pos = 0;    while(pos < c.size() - 1 && c[pos] == 0) pos++;    for(int i = pos; i < c.size(); i++){        cout << c[i];    }    cout << endl;    cout << rem << endl;    return 0;}

A. rem /= b;B. rem %= b;C. rem = b;D. rem = q;

📌 大纲对应知识点:高精度运算🎯 考查目标:掌握高精度除以低精度的核心逻辑

📋 选项详解

选项
是否正确
详细解析
A. rem /= b;
得到的是商,不是余数,后续计算错误。
B. rem %= b;
✅ 正确选项
每次计算后保留余数,参与下一位的运算,符合高精度除法逻辑。
C. rem = b;
固定余数为除数,逻辑完全错误。
D. rem = q;
将商作为余数,后续计算全部错误。

⭐ 答案:B📌 知识点:高精度除法

💡 解题小贴士:高精度除以低精度口诀:从高位到低位逐位计算,余数乘以10加当前位,除以除数得商,保留余数继续计算。


📝 学习建议

本次考试核心考点涵盖:线性表(链表操作)、数论(素数筛、最大公约数)、二分算法(二分查找、二分答案)、排序算法(快速排序、归并排序)、分治算法、高精度运算等。复习建议:1. 熟练掌握链表的插入、删除操作,注意避免断链和内存泄漏2. 掌握埃氏筛和欧拉筛的实现原理和优化逻辑,理解时间复杂度差异3. 重点练习二分查找的各类变体(lower_bound、upper_bound)和二分答案的应用场景4. 熟悉常见排序算法的时间复杂度、稳定性和适用场景5. 多做分治、高精度类的编程题,提高代码实现能力

💪 更多真题练习、薄弱点诊断,欢迎使用「GESP练题小程序」,助你高效备考GESP等级考试。

「艾墨舟编程小先锋」专为GESP考生打造的免费题库小程序。


📚 学而不思则罔,思而不学则殆。 —— 孔子

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