# 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 int counter() { static int n = 0; // 只在第一次进入时初始化一次 return ++n; } int main() { std::cout << counter() << counter() << counter(); } ``` 请写出程序的输出。 ## 【L1 · 第 3 题】代码阅读(45 秒)· 值传递 vs 引用传递 ```cpp #include void f(int x) { x = x + 1; } // 形参 x 是实参的副本 int main() { int n = 10; f(n); std::cout << n; } ``` 请写出程序的输出。 ## 【L2 · 第 4 题】代码阅读(1 分钟)· 函数重载决议 ```cpp #include 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 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 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 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 void f(int& x) { x = x + 1; } // 形参 x 是实参的别名 int main() { int n = 10; f(n); std::cout << n; } ``` 请写出程序的输出。 ## 【L2 · 第 4 题】代码阅读(1 分钟)· 函数重载决议 ```cpp #include 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 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 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 里适当加时。