Files
张宗平 6640baffae 堂前测试重做:C++98 双平行卷(A/B)+ 三级难度梯度
原版 10 题单卷难度近似、缺梯度、无平行卷,区分度不足。新版改为两份等价
平行卷(防抄袭 / 可重测),按 L1 初级 / L2 中级 / L3 进阶三级分布,补齐
对象切片、菱形继承二义、const 与指针等真正能筛出高阶学员的考点;严格限定
C++98,不涉及现代特性。所有代码题输出经 g++ -std=c++98 -pedantic-errors
实测核实,修正了 B 卷第 2 题链式 << 求值顺序未定义的缺陷(改为三行输出)。
2026-07-04 13:31:33 +08:00

394 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# C++ 基础摸底小测验(10 分钟 / 10 题 · 两份等价平行卷)
> **用途**:验证 [`OUTLINE.md`](OUTLINE.md) §0 里"学员已掌握 C++ 基础语法与 OOP 概念"这条假设
> 是否成立。建议在 **Day1 模块 B 开始前**(C++ 回顾环节之前)发给学员,闭卷、不查资料,
> **10 分钟内独立完成**。
>
> **本卷语言基线 = C++ 98**:刻意不涉及任何 C++11/14/17/20+ 现代特性(auto / 智能指针 /
> 移动语义 / lambda / override / 列表初始化等)。目的是先把"经典 C++ / OOP 这块根基本身
> 是否扎实"测清楚——这是 Qt 课程 C++ 部分的真正地基;现代特性的回顾放在测验之后、随堂进行。
>
> **两份平行卷(A / B)**:覆盖**相同的知识点梯度、相同的难度分布**,题目平行但不雷同,
> 用于以下场景:
> - **防抄袭**:相邻座席发不同卷(A/B 交错);
> - **重测**:对摸底结果存疑的学员,换卷再测一次,两次难度可比;
> - **平行对照**:A 卷统一当堂测、B 卷留作补测/作业,便于横向比较。
>
> **三级难度分布(每份卷 10 题)**:
>
> | 层级 | 题号 | 能力定位 | 答错意味着 |
> |---|---|---|---|
> | **L1 初级**(3 题)| 1–3 | 记得住、读得懂基础语法 | 根基明显不足,Day1 需回退到语法讲解 |
> | **L2 中级**4 题)| 4–7 | OOP 三大特性、内存模型有概念 | 概念有但没吃透,讲课时在对应点加时 |
> | **L3 进阶**3 题)| 8–10 | 多态细节、继承陷阱、能分析真题 | 达到"高阶"假设,可全速推进 |
>
> 题型:**选择题 + 代码阅读(判断输出/找 bug)**。每题标注建议用时,加起来约 10 分钟。
---
# A 卷
## 【L1 · 第 1 题】选择(30 秒)· 引用基础
下列关于"引用(reference"和"指针(pointer"的说法,**错误**的是?
- A. 引用必须在定义时初始化
- B. 引用可以绑定到 `nullptr`
- C. 引用一旦绑定到一个对象,就不能再改绑到另一个对象
- D. 函数参数用引用传递,可以避免实参被拷贝
## 【L1 · 第 2 题】代码阅读(45 秒)· static 局部变量
```cpp
#include <iostream>
int counter() {
static int n = 0; // 只在第一次进入时初始化一次
return ++n;
}
int main() {
std::cout << counter() << counter() << counter();
}
```
请写出程序的输出。
## 【L1 · 第 3 题】代码阅读(45 秒)· 值传递 vs 引用传递
```cpp
#include <iostream>
void f(int x) { x = x + 1; } // 形参 x 是实参的副本
int main() {
int n = 10;
f(n);
std::cout << n;
}
```
请写出程序的输出。
## 【L2 · 第 4 题】代码阅读(1 分钟)· 函数重载决议
```cpp
#include <iostream>
void h(long) { std::cout << "L"; }
void h(double) { std::cout << "D"; }
int main() { h(5); }
```
这段代码会发生什么?(编译通过则写出输出;不能编译则说明原因)
## 【L2 · 第 5 题】选择(45 秒)· 虚函数与多态
关于虚函数,下列说法**正确**的是?
- A. 只要基类析构函数不加 `virtual`,通过基类指针 `delete` 派生类对象一定没有问题
- B. 构造函数可以声明为虚函数
- C. 含有纯虚函数(`= 0`)的抽象类不能被直接实例化
- D. 派生类必须重写基类中的**每一个**虚函数,否则编译报错
## 【L2 · 第 6 题】代码阅读(1 分钟)· 构造与析构顺序
```cpp
#include <iostream>
struct M { M(){ std::cout<<"M"; } ~M(){ std::cout<<"~M"; } };
struct Base { Base(){ std::cout<<"B"; } ~Base(){ std::cout<<"~B"; } };
struct Derived : Base {
M m; // 成员对象
Derived(){ std::cout<<"D"; }
~Derived(){ std::cout<<"~D"; }
};
int main(){ Derived d; }
```
请写出构造与析构的完整输出顺序。
## 【L2 · 第 7 题】找 bug1.5 分钟)· 浅拷贝与 double free
```cpp
class Buffer {
char* data;
public:
Buffer(int n) { data = new char[n]; }
~Buffer() { delete[] data; }
};
void use() {
Buffer a(10);
Buffer b = a; // 用 a 拷贝构造 b
} // a、b 离开作用域
```
`use()` 运行到结束时会发生什么问题?请说明原因。
## 【L3 · 第 8 题】代码阅读(1 分钟)· 对象切片
```cpp
#include <iostream>
struct Base {
virtual const char* name() const { return "Base"; }
};
struct Derived : Base {
const char* name() const { return "Derived"; } // 重写虚函数
};
void print(Base b) { // 注意:按值传递,不是引用/指针
std::cout << b.name();
}
int main() {
Derived d;
print(d);
}
```
请写出程序的输出,并解释为什么不是 `Derived`
## 【L3 · 第 9 题】选择(1 分钟)· const 与指针
下列声明中,表示"**指针本身是常量**(不能再指向别的地址),但指针指向的数据可以修改"的是?
- A. `const char* p`
- B. `char const* p`
- C. `char* const p`
- D. `const char* const p`
## 【L3 · 第 10 题】代码阅读(1.5 分钟)· 多重继承的二义性
```cpp
class Base { public: int value; };
class Left : public Base {}; // 普通继承
class Right : public Base {}; // 普通继承
class Derived : public Left, public Right {};
int main() {
Derived d;
d.value = 10; //
}
```
`d.value = 10;` 这一行能编译通过吗?如果可以,`value` 来自哪条继承链路?如果不能,请说明原因。
---
---
# B 卷
> 与 A 卷一一对应、考点相同、难度相当的平行卷。
## 【L1 · 第 1 题】选择(30 秒)· 指针基础
下列关于"指针"的说法,**错误**的是?
- A. 指针可以不初始化(此时其值未定义)
- B. 指针可以在生命周期内先后指向不同的对象
- C. 指针变量本身在内存中也占用存储空间
- D. 用 `delete` 释放一个指针后,该指针自动变成空指针 `nullptr`
## 【L1 · 第 2 题】代码阅读(45 秒)· static 局部变量
```cpp
#include <iostream>
int tick() {
static int n = 100; // 只在第一次进入时初始化一次
return n--;
}
int main() {
std::cout << tick() << "\n";
std::cout << tick() << "\n";
std::cout << tick() << "\n";
}
```
请写出程序的输出(共三行)。
## 【L1 · 第 3 题】代码阅读(45 秒)· 值传递 vs 引用传递
```cpp
#include <iostream>
void f(int& x) { x = x + 1; } // 形参 x 是实参的别名
int main() {
int n = 10;
f(n);
std::cout << n;
}
```
请写出程序的输出。
## 【L2 · 第 4 题】代码阅读(1 分钟)· 函数重载决议
```cpp
#include <iostream>
void h(short) { std::cout << "S"; }
void h(float) { std::cout << "F"; }
int main() { h(7); }
```
这段代码会发生什么?(编译通过则写出输出;不能编译则说明原因)
## 【L2 · 第 5 题】选择(45 秒)· 虚函数与多态
关于虚函数,下列说法**正确**的是?
- A. 基类把析构函数声明为 `virtual`,是为保证 `delete 基类指针` 时派生类部分也能被正确析构
- B. 在构造函数里调用虚函数,会触发派生类的虚函数版本
- C. 派生类重写基类虚函数时,可以任意改变返回类型,不受限制
- D. 一个类只要有虚函数,编译器就**不会**为它生成虚函数表(vtable)
## 【L2 · 第 6 题】代码阅读(1 分钟)· 构造与析构顺序
```cpp
#include <iostream>
struct N { N(){ std::cout<<"1"; } ~N(){ std::cout<<"4"; } };
struct A { A(){ std::cout<<"2"; } ~A(){ std::cout<<"3"; } };
struct C : A {
N n; // 成员对象
C(){ std::cout<<"5"; }
~C(){ std::cout<<"6"; }
};
int main(){ C c; }
```
请写出构造与析构的完整输出顺序。
## 【L2 · 第 7 题】找 bug1.5 分钟)· 浅拷贝与 double free
```cpp
class Stack {
int* data;
int size;
public:
Stack(int n) : size(n) { data = new int[n]; }
~Stack() { delete[] data; }
};
void use() {
Stack a(10);
Stack b = a; // 用 a 拷贝构造 b
} // a、b 离开作用域
```
`use()` 运行到结束时会发生什么问题?请说明原因。
## 【L3 · 第 8 题】代码阅读(1 分钟)· 对象切片
```cpp
#include <iostream>
struct Animal {
virtual const char* sound() const { return "..."; }
};
struct Dog : Animal {
const char* sound() const { return "Woof"; } // 重写虚函数
};
const char* record(Animal a) { // 注意:按值传递,不是引用/指针
return a.sound();
}
int main() {
Dog d;
std::cout << record(d);
}
```
请写出程序的输出,并解释为什么不是 `Woof`
## 【L3 · 第 9 题】选择(1 分钟)· const 与指针
下列声明中,表示"**指针指向的数据是常量**(不能通过该指针改数据),但指针本身可以指向别的地址"的是?(本题有两个等价正确选项,全选才得分)
- A. `const char* p`
- B. `char const* p`
- C. `char* const p`
- D. `const char* const p`
## 【L3 · 第 10 题】代码阅读(1.5 分钟)· 多重继承的二义性
```cpp
class Root { public: int id; };
class A : public Root {}; // 普通继承
class B : public Root {}; // 普通继承
class Final : public A, public B {};
int main() {
Final f;
f.id = 5; //
}
```
`f.id = 5;` 这一行能编译通过吗?如果可以,`id` 来自哪条继承链路?如果不能,请说明原因。
---
---
## 答案与评分(教师用,建议测验时不发给学员)
> 所有"代码阅读 / 找 bug"题的输出与编译行为,均已用 **g++ 13.3 在 `-std=c++98
> -pedantic-errors -Wall` 严格模式下编译运行核实**,非凭记忆作答。
### A 卷答案
| 题号 | 答案 | 考查点 · 课程出处 |
|---|---|---|
| L1·1 | **B**(引用不能为空,这是它和指针的关键区别之一——引用是已绑定对象的别名,不存在"空引用") | 引用的本质 · `p01/ch03/reference_essence.cpp` |
| L1·2 | `123``static` 局部变量只初始化一次,之后跨调用保持值;三次调用依次返回 1、2、3) | static 与存储期 · `p01/ch04/s04` |
| L1·3 | `10`(值传递:`f` 内改的是 `n` 的副本,函数返回后副本销毁,`n` 不变) | 函数参数传递 · `p01/ch03` |
| L2·4 | **编译失败**`h(5)``int` 既要转 `long` 又要转 `double`,两个都是"标准转换",优先级相同 → 重载二义(`ambiguous`)。必须显式写 `h(5L)``h(5.0)` 消歧义 | 函数重载决议 · `p01/ch03/function_overload_mechanism.cpp` |
| L2·5 | **C**(A 错:非虚析构时派生类部分不会被析构,是经典内存泄漏;B 错:构造函数不能是虚函数;D 错:不重写就继承基类实现,只有纯虚函数才"必须"重写) | 虚函数与抽象类 · `p01/ch04/s08/virtual_dtor_purpose.cpp` |
| L2·6 | `BMD~D~M~B`(构造:基类 `B` → 成员 `M` → 派生类体 `D`;析构完全相反:派生类 `~D` → 成员 `~M` → 基类 `~B` | 继承+成员的构造析构顺序 · `p01/ch04/s07/inheritance_ctor_dtor_order.cpp` |
| L2·7 | **浅拷贝导致 double free**:编译器合成的默认拷贝构造是浅拷贝,`b.data``a.data` 指向**同一块** `new[]` 内存;`use()` 结束时 `~Buffer()` 被调用两次,对同一块内存 `delete[]` 两次 → 行为未定义,通常崩溃。修复:自定义拷贝构造(深拷贝),或禁用拷贝 | 深拷贝 vs 浅拷贝 · `p01/ch04/s03/deep_vs_shallow_copy.cpp` |
| L3·8 | 输出 `Base``print(Base b)` 形参**按值接收**`Derived` 对象被**切片(slicing)**:只拷贝了 `Base` 子对象,派生类新增部分被截断,`b` 是个货真价实的 `Base`,虚函数自然调到 `Base::name`。要让多态生效,形参须为 `Base&`/`Base*` | 对象切片 · `p01/ch04/s08` |
| L3·9 | **C**`char* const p` —— `const` 修饰 `p` 本身,故指针不可改指向;`*p` 未被 const 修饰,数据可改。A/B 是"指向 const 的指针"D 两边都 const | const 与指针 · `p01/ch03` |
| L3·10 | **编译失败**`d.value` 二义(`ambiguous`)。`Derived``Left``Right` 两条路径各继承了一份 `Base::value`,编译器无法判断要访问哪一个。修复:①用 `d.Left::value` 显式限定;②改用**虚继承** `class Left : virtual public Base`,让两条路径共享同一个 `Base` 子对象 | 多重继承二义性与虚继承 · `p01/ch04/s07` |
### B 卷答案
| 题号 | 答案 | 考查点 · 课程出处 |
|---|---|---|
| L1·1 | **D**`delete` 只释放指针指向的内存,**不改动指针本身**——它仍保存原地址,成为"悬空指针(dangling)";继续解引用是未定义行为。这是 C++98 没有 `nullptr` 时代的经典坑) | 指针与内存管理 · `p01/ch04/s03` |
| L1·2 | 三行依次输出 **`100` / `99` / `98`**`static` 局部变量只初始化一次、跨调用保持值;`n--` 先返回当前值再自减)。三行分开打印,无求值顺序歧义 | static 与存储期 · `p01/ch04/s04` |
| L1·3 | `11`(引用传递:`x``n` 的别名,`x=x+1` 直接改 `n` | 函数参数传递 · `p01/ch03` |
| L2·4 | **编译失败**`h(7)``int` 既要转 `short` 又要转 `float`,两个都是标准转换、优先级相同 → 重载二义(`ambiguous`)。需写 `h((short)7)``h(7.0f)` 消歧义 | 函数重载决议 · `p01/ch03/function_overload_mechanism.cpp` |
| L2·5 | **A**(A 正确,这正是虚析构存在的意义;B 错:构造期间虚表尚未就绪/对象类型还是基类,调用基类版本;C 错:返回类型必须协变或相同,不能"任意";D 错:有虚函数才会生成 vtable) | 虚函数与抽象类 · `p01/ch04/s08/virtual_dtor_purpose.cpp` |
| L2·6 | `215643`(构造:基类 `2` → 成员 `1` → 派生类 `5`;析构相反:`6``4``3` | 继承+成员的构造析构顺序 · `p01/ch04/s07/inheritance_ctor_dtor_order.cpp` |
| L2·7 | **浅拷贝导致 double free**:默认拷贝构造浅拷贝,`b.data``a.data` 同指一块 `new[]``use()` 结束时析构两次 → double free / 未定义行为。修复同 A 卷第 7 题 | 深拷贝 vs 浅拷贝 · `p01/ch04/s03/deep_vs_shallow_copy.cpp` |
| L3·8 | 输出 `...``record(Animal a)` 按值接收 → **对象切片**`Dog` 部分被截断,`a` 是个 `Animal`,调到 `Animal::sound`。要听到 `Woof`,形参须为 `Animal&`/`Animal*` | 对象切片 · `p01/ch04/s08` |
| L3·9 | **A 和 B**`const char* p``char const* p` 写法不同但完全等价——`const` 修饰的是 `*` 的左边,即指向的数据;指针本身可改。C 是 const 指针,D 两边都 const。**全选 A、B 才得分** | const 与指针 · `p01/ch03` |
| L3·10 | **编译失败**`f.id` 二义(`ambiguous`)。`Final``A``B` 两条路径各继承一份 `Root::id`,编译器无法抉择。修复:①`f.A::id` 显式限定;②虚继承 `class A : virtual public Root` | 多重继承二义性与虚继承 · `p01/ch04/s07` |
> **关于 B 卷第 2 题的设计说明**:原稿曾写成单行链式 `std::cout << tick() << tick() << tick();`
> 但该写法的**输出顺序依赖 `<<` 的求值顺序,而 C++ 未规定该顺序**unspecified behavior),
> 不同编译器可能拼出 `1009998`、`9899100` 等不同结果——会让"标准答案"本身不稳定。现稿改为
> 三行分开打印,输出确定无歧义,更适合做闭卷摸底的客观题。
### 评分与分层预案
**计分规则**:每题 1 分,满分 10 分。选择/代码阅读答对即得分;"找 bug / 解释"题(A·7、A·8、A·10、
B·7、B·8、B·10)只要**说对核心机制**(浅拷贝/double free、对象切片、二义性)即得分,不苛求
措辞。B 卷第 2 题按上述概念核分说明处理。
**分层与教学预案**(对应 `OUTLINE.md` §0):
| 分段 | 占比解读 | 指导动作 |
|---|---|---|
| **810 分** | 达到"已掌握 C++ 基础与 OOP 概念"假设 | Day1 模块 B 按原"巡礼式"节奏推进,现代特性(C++11+)正常引入 |
| **57 分** | 部分薄弱——对照上表找出**集中失分的层级** | **L1 失分多**(Q1–3):回退到基础语法讲解;**L2 失分多**(Q4–7):在构造析构/浅拷贝/重载决议处加时 5–10 分钟;**L3 失分多**(Q8–10):说明学员 OOP 基础尚可,多态/继承陷阱课上点到为止、留自学 |
| **04 分** | 明显达不到假设 | 按 `OUTLINE.md` §0 预案,Day1 模块 B **整体退回语法讲解**,练习任务保留但预期完成度下调 |
**逐题正确率用法**(大规模班级):统计每题正确率,**低于 50% 的题**对应的知识点在 Day1
模块 B 里适当加时;无需逐份细看。
### 与旧版的差异(为什么重做)
此前一版 PRETEST(10 题、单卷)的主要不足:
1. **缺梯度**——10 题难度近似,集中在"概念识别"层面,区分不出"学过"和"真正懂了"
2. **无平行卷**——单卷无法防抄袭、无法平行重测;
3. **进阶层薄弱**——没有对象切片、菱形继承这类真正能筛出"高阶"学员的题,全做对与做对 7 题
的学员水平差异,旧卷测不出来。
新版改进:**三级梯度(L1/L2/L3)+ 两份等价平行卷(A/B)+ 每题标注考查点与课程出处**,
既能给出每层级单独的掌握情况,又支持防抄袭与平行重测。
## 使用方式
- 纸质或线上表单均可,闭卷、不查资料、不讨论,**10 分钟收卷**;
- 两份平行卷按需使用:相邻座席 A/B 交错防抄袭;或 A 卷当堂测、B 卷补测/作业;
- 只用于摸底分层,**不计入** `docs/TRAINING_PLAN_2026.md` 的正式考核成绩;
- 大规模班级可只统计"每题正确率",正确率低于 50% 的题对应知识点在 Day1 模块 B 里适当加时。