← 开始学习

FIND YOUR START / 定位缺口

15 分钟,
找到合适的起点。

九道短题帮你定位 C++ 的具体缺口。总分只决定补课或申请挑战;G0 仍需要独立代码、测试、调试与口述的完整证据。

已经实际通过完整 G0? 有独立概念、陌生编程、测试调试与口述的完整通过记录,从SYS-00系统接口桥接续 →

短测高分、收藏或看过答案都不会自动通过 G0。页面不收集答案、不写入学习状态;原 Day 1 的实际验收继续保留。

还不熟悉 .cpp 怎样编译?先看从保存文件到运行的操作教程。工具准备不计入这 15 分钟;短测仍先写预测,再查看结果。

先写答案,再展开解释

  1. 用 1 分钟准备纸或本地笔记,9 分钟按顺序写答案;卡住就标记问号,不搜索、不运行代码。
  2. 最后 5 分钟再展开答案并逐题自评 0/1/2 分。先写结果,再写原因;只猜中输出最多 1 分。
  3. 如果以前已经看过题目或答案,在记录中标记“复习”。本页始终是公开短测,不能拿它替代 G0 的陌生闭卷考核。

每题 0–2 分,总分 18 分。第 02–05 题是关键题;不能用别题的高分补偿生命周期或所有权错误。暂时不会的题可以跳过,把问号留给补课。

第 01 题 · 代码预测

先算运算,再看接收类型

写出这一行输出。如果只把 i<4 改成 i<1,输出又是什么?先手算,不运行。

完整程序;先预测,再在自己的副本运行。

#include <iostream>
int main() {
    int total=1;
    for(int i=1; i<4; ++i) total+=i;
    double ratio=7/2;
    std::cout<<total<<' '<<ratio<<'\n';
}
写完后对照:第 01 题答案与评分
  1. 原输出是 7 3。total 依次为 2、4、7;7/2 先做整数除法,得到 3,赋给 double 不会恢复小数。
  2. 修改条件后的输出是 1 3:第一次条件就失败,total 保持初值 1。若要 3.5,必须在除法之前转换操作数。
  • 2 分:两组输出都正确,并解释整数除法和零次循环。
  • 1 分:两组结果只对一组,或结果都对但说不清运算类型。
  • 0 分:把 double 接收误认为会让 7/2 得到 3.5,且不能修正推理。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

第 02 题 · 代码预测 · 关键题

引用观察的是哪一个对象

输出是什么?指出 bump 修改了谁,observed 为什么会看到 add 的结果。

完整程序;先预测,再在自己的副本运行。

#include <iostream>
int bump(int value) { ++value; return value; }
void add(int& value) { value+=3; }
int main() {
    int x=4;
    bump(x);
    const int& observed=x;
    add(x);
    std::cout<<x<<' '<<observed<<'\n';
}
写完后对照:第 02 题答案与评分
  1. 输出 7 7。bump 的 value 是 x 的副本;它的自增没有改 x,返回的 5 也没有被调用者保存。
  2. add 的参数引用 x,所以 x 从 4 变为 7。observed 是同一个 x 的只读访问方式,不是过去值的快照;const 不会禁止其他合法访问路径修改 x。
  • 2 分:输出和三条关系都正确:按值副本、可写引用、只读引用仍是别名。
  • 1 分:输出正确,但把 observed 描述成复制或不能解释 const。
  • 0 分:认为 bump 改了调用方,或认为 const 引用固定了 x 的数值。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

第 03 题 · 代码预测 · 关键题

转移拥有者以后,借用还指向哪里

这一程序是否可安全输出?写结果,并说明 borrowed 有效到什么时候。

完整程序;先预测,再在自己的副本运行。

#include <iostream>
#include <memory>
#include <utility>
int main() {
    auto owner=std::make_unique<int>(9);
    const int* borrowed=owner.get();
    auto next=std::move(owner);
    std::cout<<(!owner)<<' '<<*borrowed<<'\n';
}
写完后对照:第 03 题答案与评分
  1. 这一程序输出 1 9。移动 unique_ptr 后 owner 为空,next 拥有原来的那个整数;这次移动没有搬走或销毁整数对象。
  2. borrowed 没有拥有权,也不延长寿命。这里 next 活到打印之后;若先 reset 或销毁 next,再解引用 borrowed 就不合法。不能 delete borrowed。
  • 2 分:结果、转移责任和借用寿命都解释正确。
  • 1 分:结果正确但没有说明借用依赖 next 的寿命。
  • 0 分:认为 get 增加拥有者,或认为 next 结束后 borrowed 仍能合法读取。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

第 04 题 · 纸上找错 · 关键题

返回的 view 能活多久

只做纸上分析,不运行悬空访问。调用者拿到返回值后能否读字符?给出一个直接修法。

纸上分析;不要运行悬空或失效访问。

#include <string>
#include <string_view>
std::string_view label() {
    std::string local="cpu";
    return local;
}
写完后对照:第 04 题答案与评分
  1. 不能。local 在函数返回时销毁,返回的 string_view 只借用其字符,不延长 local 的生命周期。是否看似输出 cpu 不能证明合法。
  2. 让函数按值返回 std::string,调用者保存这个拥有值即可;不要只把返回类型换成 const std::string&,那同样引用局部对象。
  • 2 分:准确定位 local 销毁、view 不拥有,并给出返回 string 值的修法。
  • 1 分:能指出悬空,但未给出正确的拥有方式。
  • 0 分:认为非空地址或短字符串优化能保证返回的 view 有效。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

第 05 题 · 纸上找错 · 关键题

reserve 之后能否沿用旧地址

只做纸上分析。假设 capacity()+1 可表达且 reserve 成功。这次 reserve 会发生重分配;最后一行可以读 p 吗?怎么改读取位置?

纸上分析;不要运行悬空或失效访问。

#include <iostream>
#include <vector>
int main() {
    std::vector<int> values{6,2};
    const int* p=values.data();
    values.reserve(values.capacity()+1);
    std::cout<<*p<<'\n';
}
写完后对照:第 05 题答案与评分
  1. 不可以。成功请求超过当前 capacity 的容量会重新分配,原元素指针、引用和迭代器失效。元素值仍是 6、2,不代表旧地址仍有效。
  2. 在 reserve 完成后重新通过 values.data() 取得指针,或直接读非空 values 的 front()。不要写出悬空读取的固定“预期输出”。
  • 2 分:明确重分配导致失效,并把取地址移到 reserve 之后。
  • 1 分:知道需要重新取地址,但不能解释 size 与 capacity 的区别。
  • 0 分:因为 size 未变、旧地址非空或元素仍为 6 而认为旧 p 安全。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

第 06 题 · 构建分析

声明够编译,为什么仍链接失败

文件按下面内容分别保存。main.cpp 已用 -c 生成 main.o;只执行 clang++ main.o -o app 却缺少 twice 的定义。失败在哪个阶段?给出补齐的两条命令。

多文件题面;按注释拆分为三个文件,不能整段保存成一个 cpp。

// calc.hpp
int twice(int value);

// calc.cpp
#include "calc.hpp"
int twice(int value) { return value*2; }

// main.cpp
#include "calc.hpp"
int main() { return twice(3)==6 ? 0 : 1; }
写完后对照:第 06 题答案与评分
  1. 这是链接阶段缺少实现。头文件中的声明足够检查调用类型,但不会自动把旁边的 calc.cpp 加入链接。
  2. 先执行 clang++ -std=c++20 -c calc.cpp -o calc.o,再执行 clang++ main.o calc.o -o app。只有链接成功后才运行 ./app;本例正常退出码为 0。
  • 2 分:区分编译与链接,补齐实现对象并说明只在成功后运行。
  • 1 分:知道缺少实现,但命令漏编译 calc.cpp 或仍运行旧产物。
  • 0 分:把 -c 生成的目标文件当可执行文件,或认为再次 include 声明能提供定义。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

第 07 题 · 代码预测与调试

一个零值,揭穿看似合理的测试

需求是统计非负读数。实际输出是什么?指出错误比较,并给出能单独揭穿它的最小输入及期望。

完整程序;先预测,再在自己的副本运行。

#include <array>
#include <iostream>
int main() {
    int count=0;
    for(const int value : std::array<int,3>{3,0,4}) {
        if(value>0) ++count;
    }
    std::cout<<count<<'\n';
}
写完后对照:第 07 题答案与评分
  1. 实际输出 2,但按需求应为 3。value>0 排除了零;应使用 value>=0。
  2. 最小反例是 [0]:正确非负个数是 1,原程序给 0。调试时在 value=0 的这一轮观察条件与 count;修复后还要检查空组、全负数与单个正数,而不只改展示文字。
  • 2 分:给出实际 2、正确 3、>= 修法和 [0] 反例。
  • 1 分:找出比较符错误,但缺少独立期望或最小反例。
  • 0 分:把正确期望改成 2,或仅换掉含零输入让测试通过。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

第 08 题 · 代码预测

复制值与移动资源分开解释

输出是什么?为什么这里不需要知道移动之后 a 曾经是不是空的?

完整程序;先预测,再在自己的副本运行。

#include <iostream>
#include <utility>
#include <vector>
int main() {
    std::vector<int> a{3,5};
    auto b=a;
    auto c=std::move(a);
    b[0]=9;
    a={2};
    std::cout<<b[0]<<' '<<c[0]<<' '<<a[0]<<'\n';
}
写完后对照:第 08 题答案与评分
  1. 输出 9 3 2。b 是独立复制,修改它不改 c;c 得到原来的 3、5。
  2. 移动后的 a 可以重新赋值;这里先赋成 {2},再读 a[0]。推理不依赖一般 moved-from 容器的内容为空,也不应把空当所有类型的统一后置条件。
  • 2 分:输出正确,解释独立副本、移动目标和重新赋值后再用来源。
  • 1 分:输出正确,但把所有移动后的对象必为空当作理由。
  • 0 分:把 b 的修改传播给 c,或在未检查/重新赋值前读取任意 moved-from 下标。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

第 09 题 · 代码预测与错误处理

解析成功是否等于整段输入合法

三个输出值分别是什么?如果合同要求输入整段都是一个整数,12x 应接受吗?

完整程序;先预测,再在自己的副本运行。

#include <charconv>
#include <iostream>
#include <string_view>
#include <system_error>
int main() {
    const std::string_view text="12x";
    int value=0;
    const auto result=std::from_chars(text.data(),text.data()+text.size(),value);
    std::cout<<(result.ec==std::errc{})<<' '<<value<<' '
             <<(result.ptr==text.data()+text.size())<<'\n';
}
写完后对照:第 09 题答案与评分
  1. 输出 1 12 0:整数前缀 12 解析成功,value 为 12,但解析停止在 x,尚未到末尾。
  2. 完整整数合同应拒绝 12x。成功条件要同时检查错误码和 ptr==end;外部值域也要按接口合同另查。合法的 0 与解析失败不能用同一个魔法数字混淆。
  • 2 分:三个值和拒绝理由都正确,同时检查错误码与完整消费。
  • 1 分:知道应拒绝尾字符,但以为错误码本身会失败。
  • 0 分:只因 value 得到 12 就接受整个字段。 未作答也记 0 分。

现在看到的是教学解释。标记看过的答案,下一次独立验收要换新题。

ROUTING ONLY / 手动记录

总分决定下一步,证据决定 G0

把九题得分相加,并单独圈出第 02–05 题是否各得 2 分。以下是学习入口建议,不是 G0 的通过分数线。

  1. 0–9 分:先从基本操作建立把握

    许多题无法独立解释时。

    从第01章开始,依次学习到09;完成09后再做M1。各章先预测、再运行、独立修改。

    打开对应入口 →
  2. 10–14 分;或高分但关键题有缺口:按缺口补课

    总分 15–18 分时,如果第 02–05 题任一未得满分,也走本入口;关键题不能靠其他题的分数补偿。

    按下方缺口表回到相应章和单元。高总分但关键题失误,也先补这个缺口。

    打开对应入口 →
  3. 15–18 分且关键题全对:可以申请完整 G0 挑战

    这只是短测路由,不表示 G0 已通过。

    按完整证据清单,由评审临时给出陌生概念题与编程变体,再实际验收。

    打开对应入口 →

如果以前做过这些题,即使全对,也把结果当复习反馈。要判断能否进入主项目,请用陌生题与完整证据清单

只补你真正卡住的地方

按题目对应的知识入口补课,并完成预测、运行、修改与新输入检查。短测用于定位缺口,不能替代完整G0。

查看当前01–18学习顺序 → · 贯穿案例与调试 →

EVIDENCE BEFORE PROGRESSION

申请完整 G0 挑战

已有完整、可复核的 G0 通过记录,可以直接进入 W5 第一个单元,不必为此补做已掌握的 Day 1。只有短测高分或已看完教材时,先完成下面的真实挑战。四类要求不可互相补偿;原 Day 1 状态不自动通过,继续该本地课程或进入 MacPerf 时仍遵守各自验收。

零基础学习者先依次完成基础01–18,最后做第18章的独立练习;完整G0另计10小时,按下面的陌生题与四类证据实际验收,不把阅读或教材示例通过换成个人通过。

门槛来自共同 G0 门槛。下面把它展开为一份可执行的提交清单;短测不会替代其中任何一项。

概念:闭卷至少 80%,关键题全部正确

  • 让评审围绕新基础01–18章的验收范围(不含只认读内容)临时准备 20 道未展示的概念/预测题,每题 2 分,保留题面、本人原答案与逐题理由。达到至少 32/40;reference、ownership、dangling、vector invalidation 的关键题必须全部满分。
  • 本页九题、已展开答案的练习与已背过的输出只算复习。任何看过提示或用到助手的部分如实记录,需换新题再查独立性。
  • 保存 concept-answers.md 和评审结论;百分比只属于这次完整概念考核,不由短测总分换算。

代码:从空文件独立完成陌生小程序

  • 评审当场选择一个未做过的 C++20 小任务,明确参数、返回值、允许值域、空输入、失败行为和不可修改的 required tests。使用标准容器与清楚接口。
  • 可采用最多 30 条、数值在 [-50,50] 的小记录作为范围;具体业务、操作与新输入由评审临时决定。这里只说明规模,不提供一份可以背答案的验收题。
  • 保存本人源码、开始/结束时间、帮助记录;提交后再接受一条未预告的需求或输入变体,并独立修改。不会现编时先补章,不靠背现有案例通过。

验证:三种构建、本人边界与一次真实调试

  • 先手算独立期望,再加入本人边界测试:空、单项、重复、零/负数、合法上下界和越界拒绝;保留 required tests,注册数量必须非零。具体期望由本次新题合同决定。
  • 严格 warnings、Debug、Release、ASan/UBSan 均要实际通过。保存完整命令、编译器版本、stdout/stderr、退出码和测试数量;构建失败不运行同名旧程序。
  • 记录一次真实失败到修复:最小复现、断点/变量或错误诊断、原因、代码修改与回归。若没有自然发生的错误,可由评审明确注入一个逻辑错误;记录“注入”,不要虚构事故。

口述:不看代码解释设计和局限

  • 各用约 1 分钟解释参数与返回值、拥有与借用寿命、copy/move 的选择、一次调试的证据链。
  • 说明测试覆盖了哪些边界、sanitizer 检查了哪些已执行路径,以及没有证明什么。
  • 把评审逐项意见记入 review.md。只有四类要求都满足后,实际评审才可记录 G0;本页没有“通过 G0”的按钮,也不写学习状态。

陌生题怎样实际开始

  1. 约定一场独立挑战

    • 对评审说:“请依据共同 G0,临时给我 20 道新概念题与一个陌生 C++20 小程序。不要复用我看过答案的题;先给输入合同、required tests 和计分规则,不给解答。”
    • 列出自己已看过的章内题与公开案例。可以约 90–120 分钟进行首次尝试,时间是组织建议;到点不会自动算通过。
  2. 先作答,再公开反馈

    • 这次陌生题在自己的新练习目录作答,保留教材源码作为基线。完整 G0 通过后可进入 W5;原 Day 1 不自动记为通过,若继续该本地课程则按原验收。闭卷阶段不展开本页答案、搜索或让助手写实现。
    • 先保存原答案与源码,再让评审给反馈;若中途需要教学提示,记录帮助,并在补课后另换陌生变体检验。
  3. 提交完整证据,再进入项目

    • 提交 concept-answers.md、本人源码、边界期望表、三种构建日志、debugging.md、help-log.md 和 review.md。
    • 评审临时给一个相邻新输入或操作变化,要求解释并修改;四项全过才能进入 W5 主项目。失败项按缺口补课,继续沿同一 Recovery 路线。
展开:单文件挑战的构建命令

假设自己的源码和测试入口都在 g0.cpp,在该练习目录执行。若题目要求多文件,把各个实际源文件都列入命令。测试失败必须以非零退出,不能只打印“失败”;不用会被 NDEBUG 关闭的 assert 作为唯一验收。

单文件挑战的 Debug 检查

mkdir -p build
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread -g -O0 g0.cpp -o build/g0-debug && ./build/g0-debug

同一份代码与测试的 Release 检查

mkdir -p build
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread -O2 -DNDEBUG g0.cpp -o build/g0-release && ./build/g0-release

独立的 ASan + UBSan 检查

mkdir -p build
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer g0.cpp -o build/g0-sanitize && ASAN_OPTIONS=halt_on_error=1 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./build/g0-sanitize

每次执行后立即记录退出码与实际测试数量,并保留标准输出及错误。代码中的 && 让编译失败时停止后续运行;它不会代替测试断言。Sanitizer 的无报告只覆盖已执行路径,不能证明所有输入都正确。

展开:评审口述检查角度
  • 一个返回值是独立拥有数据,还是借用调用方的对象?最后一次合法读取在哪里?
  • 为什么这个地方使用复制或移动?移动后的来源有哪些操作被合同允许?
  • 如果只测一个普通输入,会漏掉哪个边界?你加的检查怎样独立得到期望?
  • 这次调试观察到了哪个变量状态?它如何排除其他猜测?

这些是公开的考察方向;评审应针对本次新程序追问具体值、分支与边界,不把背诵本页措辞当作通过。