GESP 客观题评测系统

2026-06-Level-6

2026-06-Level-6

试卷解析总览,可直接查看每题答案与解析。

单选题

1 题(单选题2 分)

下列关于 C++ 中继承和多态的描述中,错误的是()。

A.
通过基类指针调用虚函数时,会根据对象实际类型决定调用版本
B.
基类析构函数常声明为虚函数,以便通过基类指针正确释放派生类对象。
C.
派生类可以重写基类中的虚函数。
D.
构造函数可以声明为 virtual,以便在构造对象时实现动态绑定。

正确答案D

解析详情

【答案】D

【考点】虚函数与动态绑定

【解析】 C++ 构造函数不能声明为 virtual;构造对象时,其动态类型尚未完整建立,不能通过虚构造函数实现动态绑定。虚函数可由派生类重写,而基类析构函数通常应声明为虚函数。

【易错点】 不要把“析构函数宜为虚函数”误套到构造函数上。

2 题(单选题2 分)

下列代码中,d1->work(); 和 d2->work(); 输出不同结果的主要原因是()。

class Device {
    public:
        virtual void work() {
            cout << "Device is working" << endl;
        }
        virtual ~Device() {}
};

class Printer : public Device {
    public:
        void work() override {
            cout << "Printer is printing" << endl;
        }
};

class Scanner : public Device {
    public:
        void work() override {
            cout << "Scanner is scanning" << endl;
        }
};

int main() {
    Device* d1 = new Printer();
    Device* d2 = new Scanner();
    d1->work();
    d2->work();
    delete d1;
    delete d2;
    return 0;
}
A.
Printer 和 Scanner 使用了相同的构造函数。
B.
work() 是虚函数,且 d1 和 d2 实际指向不同派生类对象,发生动态绑定。
C.
d1 和 d2 是不同的指针变量。
D.
程序中使用了 delete 释放对象。

正确答案B

解析详情

【答案】B

【考点】虚函数与运行时多态

【解析】 work() 被声明为虚函数,调用版本由对象的实际类型决定。d1 实际指向 Printer,调用 Printer::work();d2 实际指向 Scanner,调用 Scanner::work(),所以输出不同。

【易错点】 动态绑定取决于实际对象类型,不取决于指针变量名是否不同。

3 题(单选题2 分)

下面代码在 main() 中有一行会导致编译错误,请找出来。

class Student {
public:
    Student(string n, int s) : name(n), score(s) {}
    string getName() { return name; }
    void setScore(int s) { score = s; }
private:
    string name;
    int score;
};

int main() {
    Student stu("Tom", 85);
    cout << stu.getName(); // ①
    stu.setScore(90);      // ②
    stu.score = 100;       // ③
    cout << stu.getName(); // ④
    return 0;
}
A.
第 ① 行
B.
第 ② 行
C.
第 ③ 行
D.
第 ④ 行

正确答案C

解析详情

【答案】C

【考点】类的访问控制

【解析】 score 声明在 private 区域,只能由 Student 的成员函数或友元访问。第 ③ 行在 main() 中直接写 stu.score,违反私有访问权限;通过公有函数 setScore() 修改是合法的。

【易错点】 对象存在并不意味着外部代码可以直接访问其 private 成员。

4 题(单选题2 分)

某文本编辑器把用户输入的字符依次压入栈 S。用户依次输入 X, Y, Z, W 后,连续执行两次撤销操作。每次撤销都会弹出栈顶一个字符。此时栈从栈底到栈顶的内容是()。

A.
X Y
B.
X Y Z
C.
Y Z
D.
X Z

正确答案A

解析详情

【答案】A

【考点】栈的后进先出

【解析】 依次压入 X、Y、Z、W 后,栈顶是 W。两次撤销依次弹出 W 和 Z,剩余元素从栈底到栈顶为 X、Y。

【易错点】 栈按后进先出弹出,不能从最早输入的 X 开始删除。

5 题(单选题2 分)

假设循环队列数组长度为 N = 7,队空判断条件为 front == rear。入队和出队操作如下:

const int N = 7;
int q[N];
int front = 3, rear = 3;

void enqueue(int x) {
    q[rear] = x;
    rear = (rear + 1) % N;
}

void dequeue() {
    front = (front + 1) % N;
}

依次执行:

enqueue(10);
enqueue(20);
enqueue(30);
dequeue();
enqueue(40);
dequeue();
enqueue(50);

最终(front, rear)的值是()。

A.
(5, 1)
B.
(4, 0)
C.
(5, 0)
D.
(3, 1)

正确答案A

解析详情

【答案】A

【考点】循环队列下标更新

【解析】 front 和 rear 初值均为 3。三次入队使 rear 依次变为 4、5、6;出队后 front=4;再入队得 rear=0,再出队得 front=5,最后入队得 rear=1,因此为 (5, 1)。

【易错点】 rear 从 6 再前进时要按模 7 回到 0。

6 题(单选题2 分)

以下函数 check() 用于判断一棵二叉树是否为()。

bool check(TreeNode* root) {
    if (!root) return true;

queue<TreeNode*> q;
    q.push(root);

bool hasNull = false;

while (!q.empty()) {
        TreeNode* cur = q.front();
        q.pop();

if (cur == nullptr) {
            hasNull = true;
        } else {
            if (hasNull) return false;
            q.push(cur->left);
            q.push(cur->right);
        }
    }

return true;
}
A.
满二叉树
B.
完全二叉树
C.
二叉搜索树
D.
平衡二叉树

正确答案B

解析详情

【答案】B

【考点】完全二叉树判定

【解析】 代码按层序把左右孩子(包括 nullptr)入队。一旦遇到空位置便令 hasNull=true;若之后又遇到非空结点,说明层序中出现“空位后还有结点”,不满足完全二叉树定义。

【易错点】 完全二叉树允许最后一层不满,但结点必须从左到右连续排列。

7 题(单选题2 分)

以下代码实现了二叉树的哪种遍历方式?

void traverse(TreeNode* root) {
    if (root == nullptr) return;

cout << root->val << " ";
    traverse(root->left);
    traverse(root->right);
}
A.
前序遍历
B.
中序遍历
C.
后序遍历
D.
层序遍历

正确答案A

解析详情

【答案】A

【考点】二叉树前序遍历

【解析】 函数先输出 root->val,再递归遍历左子树和右子树,访问次序是“根—左—右”,即前序遍历。

【易错点】 判断遍历类型要看根结点在左右子树递归调用之前还是之后访问。

8 题(单选题2 分)

已知一棵二叉树的先序遍历序列为:A B D E H C F G,中序遍历序列为:D B H E A F C G,则该二叉树的后序遍历序列是()。

A.
D H E B F G C A
B.
D E H B F G C A
C.
H D E B F C G A
D.
D H E B G F C A

正确答案A

解析详情

【答案】A

【考点】由先序和中序还原二叉树

【解析】 先序首项 A 是根,中序将其分为左子树 D B H E 和右子树 F C G。左子树后序为 D H E B,右子树后序为 F G C,最后访问根 A,得到 D H E B F G C A。

【易错点】 用中序划分左右子树时,两侧结点数也要同步用于切分先序序列。

9 题(单选题2 分)

有6个字符,它们出现的次数分别为: \{3,4,7,8,12,15\} ,现在用哈夫曼编码为这些字符编码,最小加权路径长度 WPL 的值为()。

A.
113
B.
119
C.
126
D.
31

正确答案B

解析详情

【答案】B

【考点】哈夫曼树的加权路径长度

【解析】 每次合并最小的两个权值:3+4=7,7+7=14,8+12=20,14+15=29,20+29=49。WPL 等于各次合并值之和,即 7+14+20+29+49=119。

【易错点】 新生成的权值必须放回集合重新参与从小到大的合并。

10 题(单选题2 分)

对 n 个不同符号进行哈夫曼编码。若生成的哈夫曼树共有 63 个结点,则 n 的值是()。

A.
31
B.
32
C.
63
D.
64

正确答案B

解析详情

【答案】B

【考点】哈夫曼树的结点数

【解析】 含 n 个符号的哈夫曼树有 n 个叶结点,且每个非叶结点都有两个孩子,因此总结点数为 2n-1。由 2n-1=63,解得 n=32。

【易错点】 63 是叶结点与内部结点的总数,不是符号数。

11 题(单选题2 分)

在格雷码中,相邻两个编码只能有一位不同。若当前编码为 110,则它的下一个编码不可能是()。

A.
010
B.
111
C.
100
D.
001

正确答案D

解析详情

【答案】D

【考点】格雷码的相邻性

【解析】 比较 110 与各编码的对应位:010、111、100 都只改变一位;001 的三位均与 110 不同,不可能作为满足相邻规则的下一个编码。

【易错点】 要比较对应位的差异数量,不能按二进制数值是否接近判断。

12 题(单选题2 分)

给定一棵二叉树,采用广度优先搜索 BFS 返回其右视图,其中右视图中的每个节点都是该层最右侧的节点。横线处应填写()。

vector<int> rightSideView(TreeNode* root) {
    vector<int> result;
    if (!root) return result;

queue<TreeNode*> q;
    q.push(root);

while (!q.empty()) {
        int sz = q.size();

for (int i = 0; i < sz; ++i) {
            TreeNode* node = q.front();
            q.pop();
        }

if (node->left) q.push(node->left);
        if (node->right) q.push(node->right);
    }

return result;
}
A.
if (i == 0) result.push_back(node->val);
B.
if (i == sz - 1) result.push_back(node->val);
C.
result.push_back(q.front()->val);
D.
if (node->right) result.push_back(node->right->val);

正确答案B

解析详情

【答案】B

【考点】二叉树层序遍历

【解析】 sz 记录当前层的结点数,循环按从左到右的顺序取出这一层结点。因此 i==sz-1 时的 node 正是该层最右侧结点,应将 node->val 加入 result。

【易错点】 必须使用进入本层循环前固定的 sz,不能用遍历中不断变化的队列长度判断。

13 题(单选题2 分)

下面代码实现二叉搜索树的插入操作。假设树中不存在重复值,横线处应填写()。

TreeNode* insertNode(TreeNode* root, int x) {
    if (root == nullptr) {
        return new TreeNode(x);
    }
    if (x < root->val) {
        __________________________
    } else {
        root->right = insertNode(root->right, x);
    }
    return root;
}
A.
root->left = insertNode(root->left, x);
B.
root = insertNode(root->left, x);
C.
root->right = insertNode(root->left, x);
D.
insertNode(root->left, x);

正确答案A

解析详情

【答案】A

【考点】二叉搜索树插入

【解析】 当 x<root->val 时应递归插入左子树。递归可能创建新的子树根,因此必须把返回值赋给 root->left,即 root->left = insertNode(root->left, x)。

【易错点】 只调用 insertNode(root->left, x) 而不接收返回值,会丢失空位置中新建的结点。

14 题(单选题2 分)

给定一个整数数组 a,每个元素表示一个位置上的数值。要求从数组中选择若干个元素,使得任意两个被选择的元素在原数组中都不相邻,并且所选元素的总和最大。函数 choose(vector<int>& a) 返回能够得到的最大总和,则横线处应填写()。

int choose(vector<int>& a) {
    if (a.empty()) return 0;

int n = a.size();
    if (n == 1) return a[0];

vector<int> dp(n, 0);
    dp[0] = a[0];
    dp[1] = max(a[0], a[1]);

for (int i = 2; i < n; ++i) {
        dp[i] = ___;
    }

return dp[n - 1];
}
A.
dp[i - 1] + a[i]
B.
max(dp[i - 1], dp[i - 2] + a[i])
C.
max(dp[i - 2], a[i])
D.
dp[i - 1] + dp[i - 2]

正确答案B

解析详情

【答案】B

【考点】线性动态规划

【解析】 对位置 i 有两种选择:不选 a[i],最大和为 dp[i-1];选择 a[i],因不能选相邻元素,最大和为 dp[i-2]+a[i]。取两者较大值得 dp[i]=max(dp[i-1], dp[i-2]+a[i])。

【易错点】 选择 a[i] 时不能从 dp[i-1] 转移,否则可能同时选中相邻元素。

15 题(单选题2 分)

下面代码实现 0/1 背包的一维动态规划。第 i 个物品重量为 wt[i],价值为 val[i],背包容量为 W。横线处应填写()。

int knapsack(int W, vector<int>& wt, vector<int>& val) {
    int n = wt.size();
    vector<int> dp(W + 1, 0);
    for (int i = 0; i < n; ++i) {
        for (int w = W; w >= wt[i]; --w) {
            __________________________
        }
    }
    return dp[W];
}
A.
dp[w] = max(dp[w], dp[w - wt[i]] + val[i]);
B.
dp[w] = max(dp[w - 1], dp[w - wt[i]] + val[i]);
C.
dp[w] = dp[w] + val[i];
D.
dp[w - wt[i]] = max(dp[w], val[i]);

正确答案A

解析详情

【答案】A

【考点】0/1 背包一维动态规划

【解析】 dp[w] 表示容量为 w 时的最大价值。处理物品 i 时,比较“不选”的 dp[w] 与“选入”的 dp[w-wt[i]]+val[i];容量从大到小枚举,保证每件物品最多使用一次。

【易错点】 若容量从小到大更新,同一物品可能在本轮被重复选取,变成完全背包。

判断题

1 题(判断题2 分)

C++ 中构造函数可以声明为虚函数,从而实现运行时多态。

正确答案错误

解析详情

【答案】错误

【考点】虚函数限制

【解析】 C++ 构造函数不能声明为 virtual。构造期间对象的派生部分尚未完整建立,不能依靠虚构造函数实现运行时多态。

【易错点】 析构函数可以且常应为虚函数,但构造函数不可以。

2 题(判断题2 分)

通过指向 Base 的指针删除 Derived 对象时,一定会先调用 Derived 的析构函数,再调用 Base 的析构函数。

#include <iostream>
using namespace std;

class Base {
public:
    ~Base() { cout << "Base destructor" << endl; }
};
class Derived : public Base {
public:
    ~Derived() { cout << "Derived destructor" << endl; }
};
int main() {
    Base* p = new Derived();
    delete p;
    return 0;
}

正确答案错误

解析详情

【答案】错误

【考点】虚析构函数

【解析】 Base 的析构函数未声明为 virtual,通过 Base* 执行 delete p 属于未定义行为,不能保证先调用 Derived 析构函数再调用 Base 析构函数。应将 Base::~Base() 声明为虚函数。

【易错点】 只要可能通过基类指针删除派生类对象,基类析构函数就应为 virtual。

3 题(判断题2 分)

在 C++ STL 中,stack 的 pop() 函数会返回栈顶元素并将其删除。

正确答案错误

解析详情

【答案】错误

【考点】STL stack 接口

【解析】 stack::pop() 只删除栈顶元素,返回类型为 void。若要取得栈顶值,应先调用 top() 保存该值,再调用 pop() 删除。

【易错点】 不要把 C++ 的 pop() 与某些语言中“弹出并返回元素”的接口混淆。

4 题(判断题2 分)

程序运行后会输出 2 。

int main() {
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);
    q.pop();
    cout << q.front() << endl;
    return 0;
}

正确答案正确

解析详情

【答案】正确

【考点】队列的先进先出

【解析】 队列依次加入 1、2、3,q.pop() 删除队首的 1。此时新的队首是 2,所以 q.front() 输出 2。

【易错点】 queue::pop() 删除的是队首,不是最后入队的元素。

5 题(判断题2 分)

下列函数试图将整数 x 插入到一棵二叉搜索树中。假设二叉搜索树满足如下性质:对于任意结点,左子树中所有结点的值均小于该结点的值,右子树中所有结点的值均大于或等于该结点的值。判断该函数是否能够在插入后保持二叉搜索树性质。

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

TreeNode* insertNode(TreeNode* root, int x) {
    if (root == nullptr) {
        return new TreeNode(x);
    }

if (x < root->val) {
        root->right = insertNode(root->right, x);
    } else {
        root->left = insertNode(root->left, x);
    }

return root;
}

正确答案错误

解析详情

【答案】错误

【考点】二叉搜索树插入

【解析】 当 x<root->val 时,代码却递归插入 root->right;反之插入 root->left,方向与题设的搜索树性质相反。因此插入后不能保证左小右大。

【易错点】 不仅要检查比较条件,还要核对递归写入的是 left 还是 right。

6 题(判断题2 分)

哈夫曼编码一定唯一,只要字符频率相同,得到的编码也一定完全相同。

正确答案错误

解析详情

【答案】错误

【考点】哈夫曼编码的非唯一性

【解析】 权值相同时,最小权值结点的选择顺序可能不同;同一棵树的左右孩子也可以互换并交换 0、1。因而相同频率可以得到不同的具体编码,但最优 WPL 相同。

【易错点】 哈夫曼编码保证最优前缀码,不保证每个字符的码字唯一。

7 题(判断题2 分)

若用数组按层序存储完全二叉树,且根节点下标为 0,则下标为 i 的节点左孩子下标为 2 \times i + 1 ,右孩子下标为 2 \times i + 2 。

正确答案正确

解析详情

【答案】正确

【考点】完全二叉树的顺序存储

【解析】 根下标从 0 开始时,每层按从左到右连续存储。下标 i 的左、右孩子位置分别为 2i+1 和 2i+2,题干公式正确。

【易错点】 若根下标从 1 开始,孩子下标才是 2i 和 2i+1。

8 题(判断题2 分)

以下代码可以正确地按层换行输出二叉树的节点值。

void printByLevel(TreeNode* root) {
    if (!root) return;

queue<TreeNode*> q;
    q.push(root);

while (!q.empty()) {
        for (int i = 0; i < q.size(); ++i) {
            TreeNode* cur = q.front();
            q.pop();
            cout << cur->val << " ";
            if (cur->left) q.push(cur->left);
            if (cur->right) q.push(cur->right);
        }
        cout << endl;
    }
}

正确答案错误

解析详情

【答案】错误

【考点】二叉树分层遍历

【解析】 for 的条件每次都会重新计算 q.size(),而循环中既弹出当前层结点又加入下一层孩子,队列长度会变化,不能稳定表示当前层结点数。应在循环前保存 int sz=q.size(),再循环 sz 次。

【易错点】 分层 BFS 的层大小必须在处理该层之前固定下来。

9 题(判断题2 分)

使用栈非递归实现二叉树前序遍历时,若希望先访问左子树,通常应先将右孩子入栈,再将左孩子入栈。

正确答案正确

解析详情

【答案】正确

【考点】非递归前序遍历

【解析】 栈是后进先出。先压入右孩子、再压入左孩子,出栈时左孩子会先于右孩子被访问,从而保持前序遍历的“根—左—右”顺序。

【易错点】 入栈顺序与期望的访问顺序相反。

10 题(判断题2 分)

动态规划问题通常要求具有最优子结构,并且常常存在重叠子问题。

正确答案正确

解析详情

【答案】正确

【考点】动态规划适用条件

【解析】 动态规划用子问题的最优解构造原问题的最优解,体现最优子结构;将重复出现的子问题结果保存并复用,避免重复计算,利用了重叠子问题。

【易错点】 只有最优子结构而没有重叠子问题时,分治法可能更直接。