# 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 题】找 bug（1.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 题】找 bug（1.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）：

| 分段 | 占比解读 | 指导动作 |
|---|---|---|
| **8–10 分** | 达到"已掌握 C++ 基础与 OOP 概念"假设 | Day1 模块 B 按原"巡礼式"节奏推进，现代特性（C++11+）正常引入 |
| **5–7 分** | 部分薄弱——对照上表找出**集中失分的层级** | **L1 失分多**（Q1–3）：回退到基础语法讲解；**L2 失分多**（Q4–7）：在构造析构/浅拷贝/重载决议处加时 5–10 分钟；**L3 失分多**（Q8–10）：说明学员 OOP 基础尚可，多态/继承陷阱课上点到为止、留自学 |
| **0–4 分** | 明显达不到假设 | 按 `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 里适当加时。
