C++ 第二周

开发环境、工程结构、编译与调试

第二周的核心不在“写更多语法”,而在“建立可持续开发的工作流”。你会发现,真正拉开差距的不是能不能敲出 if 和 while,而是能不能稳定地组织工程、定位问题、持续迭代。

学完这一周,你可以从零创建一个规范的 C++ 小工程,完成编译、运行、调试、修复与提交,形成自己的可复用开发流程。

C++ 第二周封面图

2.1 为什么第二周必须先学工程化

很多初学者会问:我会写代码了,为什么还要学目录结构、编译参数、调试器?答案很简单:当代码从 30 行变成 300 行时,随手写的方式会迅速失控。你可能遇到“改一处坏三处”“文件找不到”“报错看不懂”“功能一多就不敢动”。这些问题本质上不是语法问题,而是工程组织问题。

工程化的目标是让项目可维护、可扩展、可交接。它要求我们在写第一行代码前,先约定结构和规则。比如哪些文件放在 `src`,哪些声明放在 `include`,编译产物放在 `build`,说明文档写在 `README`。这样做的好处是:你未来每次打开项目都知道从哪里开始,队友也能快速接手。

第二周建立这套习惯,后面第三周做项目发布、第四周做优化时会省下大量时间。反过来,如果现在忽视规范,后续每增加一个功能都可能付出更高维护成本。所以本周看似“慢”,其实是在为后续速度打基础。

本周目标:从“会写代码”升级到“能稳定把代码做成项目”。

2.2 开发环境与工具链认知

一个 C++ 程序从源码变成可执行文件,至少要经过“预处理、编译、汇编、链接”几个阶段。我们不要求第一周就背所有细节,但第二周必须知道:编译器负责把源码翻译成目标文件,链接器负责把多个目标文件和库拼装成最终程序。理解这条链路后,报错信息就不再是“天书”。

常用组合包括:编辑器/IDE(Visual Studio、VS Code、CLion 等)+ 编译器(g++ / clang++ / MSVC)+ 构建系统(CMake 或直接命令行)。初学阶段你可以先用最直接的命令行构建,掌握原理后再迁移到 IDE 自动化流程。这样不会被工具界面牵着走。

环境检查建议三步走:先检查编译器版本,再跑最小 Hello World,再验证断点调试是否可用。只要这三步都通过,说明工具链可用。若任一步失败,先修环境,不要急着写业务功能。这个顺序能避免后面“代码没问题却跑不起来”的误判。

g++ --version

# hello.cpp
# include <iostream>
# int main(){ std::cout << "hello\n"; }

g++ hello.cpp -std=c++17 -O2 -o hello
./hello

2.3 工程目录结构与命名规范

目录结构不是形式主义,它直接影响开发效率。推荐初学阶段使用以下最小结构:`src` 放实现、`include` 放声明、`build` 放构建产物、`docs` 放说明、`tests` 放测试样例。这样做的最大好处是“定位快”:找实现看 `src`,找接口看 `include`,找输出看 `build`。

命名规范上,建议统一小写加下划线,例如 `score_service.cpp`、`input_utils.h`。类名可以使用驼峰,如 `ScoreService`。文件命名不统一会导致跨平台兼容问题,比如大小写敏感系统下会出现包含失败。第二周就把命名规范定下来,后续团队协作会非常顺畅。

同时要避免把所有逻辑写进 `main.cpp`。`main` 只负责流程编排,具体功能应该拆到独立函数或模块中。这样当需求变化时,你只改对应模块,不会牵一发而动全身。这种“解耦”意识是进入中级开发者的关键门槛。

project/
  include/
    score_service.h
    io_utils.h
  src/
    main.cpp
    score_service.cpp
    io_utils.cpp
  build/
  docs/
    README.md
  tests/
    case1.txt
    case2.txt

2.4 编译、链接与常见报错解读

第二周建议你养成“先读第一条关键报错”的习惯。大多数报错会连锁出现,后面的往往是前面问题的结果。比如缺少头文件会导致后续类型未定义;函数声明与定义不一致会引发链接失败。先解决最先出现的根因,往往后面错误会自动消失。

最常见三类问题:语法错误(如分号缺失、括号不匹配)、声明问题(如函数未声明就调用)、链接问题(如声明了函数但没有对应实现文件参与编译)。每类错误都可定位到具体阶段。你不需要死记代码片段,只要学会判断属于哪一类,就能迅速缩小排查范围。

建议把编译命令写成固定脚本,避免每次手打出错。比如 Windows 下使用 `build.bat`,Linux/macOS 下使用 `build.sh`。固定命令带来的稳定性远高于临时拼接参数,也更利于课堂演示和同伴复现。

g++ src/main.cpp src/score_service.cpp src/io_utils.cpp \
    -Iinclude -std=c++17 -Wall -Wextra -O2 -o build/app

2.5 调试方法:断点、单步、观察变量

调试不是“碰运气改代码”,而是可重复的方法。标准流程是:复现问题 -> 缩小范围 -> 验证假设 -> 修复 -> 回归测试。第二周重点训练“缩小范围”。断点应打在分支入口、循环边界和关键变量更新处,这些位置最容易暴露错误路径。

单步调试时重点看三件事:当前执行到哪行、关键变量值是否符合预期、接下来进入哪条分支。如果变量在某一行突然异常,说明问题通常在该行或该行之前的赋值逻辑。通过“观察变量变化轨迹”,你可以把抽象 bug 变成具体定位。

还要建立“日志调试”习惯。并非所有场景都方便开调试器,特别是上线后问题复现困难时,结构化日志非常关键。日志建议包含模块名、关键参数、结果状态。第二周就开始用规范日志,后面项目排障会快很多。

调试口诀:先复现,再定位;先证据,再修改;修完必须回归。

2.6 课堂实战:成绩统计器 v2(工程化版)

本周综合项目是“成绩统计器 v2”。与第一周不同,这次不仅要求功能正确,还要求工程结构规范。核心功能包括:读取多名学生成绩、计算平均分、输出最高分与最低分、给出及格率。建议把输入输出、统计逻辑拆分成不同文件,实现模块职责清晰。

推荐分工:`io_utils` 负责读取与输出,`score_service` 负责计算统计,`main` 负责调用流程。这样你在新增“按分数区间统计人数”时,只需改服务模块,不必动输入输出模块。这个练习会让你直观体会“模块化”带来的维护优势。

// score_service.h
#pragma once
#include <vector>

double calcAverage(const std::vector<int>& scores);
int calcMax(const std::vector<int>& scores);
int calcMin(const std::vector<int>& scores);

// main.cpp
#include <iostream>
#include <vector>
#include "score_service.h"
using namespace std;

int main() {
    vector<int> scores = {88, 75, 92, 60, 47};
    cout << "平均分: " << calcAverage(scores) << "\n";
    cout << "最高分: " << calcMax(scores) << "\n";
    cout << "最低分: " << calcMin(scores) << "\n";
    return 0;
}

2.7 质量保障:代码风格、测试与提交记录

第二周开始,建议你把“可读性”当成和功能同样重要的目标。请统一缩进、空格、括号风格,避免“能跑但难读”的代码。你未来维护自己项目时,会感谢现在的规范。建议每个函数不超过一个核心职责,函数名体现动词语义,比如 `loadScores`、`printSummary`。

测试方面,至少准备正常输入、边界输入、异常输入三类样例。很多同学只测“理想场景”,一旦数据偏离就暴露问题。测试不是找茬,而是提前消除上线风险。把测试当作开发的一部分,而不是最后补的任务。

提交记录建议遵循“小步提交”原则。每次只改一个主题,并写清楚提交说明,例如“feat: add score average function”或“fix: handle empty input list”。这会让回滚、排查和协作都更高效。

2.8 本周练习与自测清单

本周练习

  1. 完成“成绩统计器 v2”工程,目录结构规范。
  2. 至少拆分 2 个 `.h` 和 2 个 `.cpp` 文件。
  3. 提供可复现的构建命令与运行说明。
  4. 提交 6 组测试样例与对应输出结果。

自测清单

请记住:第二周不是“拼功能数量”,而是“把功能放进可维护工程”。这条路走顺后,第三周的控制流和函数设计会轻松很多,项目质量也会明显提升。

返回 C++ 第一阶段