# 一、用VSCode提升C/C++开发体验


<!--more-->

## 用 VSCode 日常写代码

上一篇文章里，你已经把源代码到二进制文件整条链路亲手走了一遍，代价是 —— 每改一次代码，都要回到终端重新敲一遍 `gcc -g -Wall -fexec-charset=GBK hello.c -o hello`；想看一个变量的变化，得反复敲 `next` 和 `print`。作为程序员，毫无疑问，偷懒是我们的美德 😎 ，那能不能简化这个过程，让我不要重复做这些操作呢？这也是程序员奉行的经典原则之一：**DRY原则**
> [!info]DRY原则
> **DRY（Don't Repeat Yourself）**&#8203; 出自《程序员修炼之道》——
> *Every piece of knowledge must have a single, authoritative representation within a system.*
> 注意宾语是 **knowledge**：它管的是"同一个决定只写一次"，
> 不是"两段代码长得像"。反面的自嘲缩写是 WET（Write Everything Twice）。
>
> 所以我们现在也要把从源代码编译成二进制这一整套动作搬进 VSCode。而不是在终端一次次地重复敲命令。VSCode更像一间整理好的书房：工具还在原地（你的电脑上），只是都放到了顺手的位置（VSCode替你调用）。而你要做的第一件事，是搞清楚**它到底替你把什么收了起来**。

![一个简笔人物把工具箱和书本搬进一间明亮整洁的小房间，屋里有桌子和椅子，墙上贴着几张空白便签](images/illus-18-vscode.jpg "工具没变，只是搬进了一间整理好的房间")

### 0、安装 VSCode

不管哪个系统，都从 `https://code.visualstudio.com/` 下载。区别只在最后那几步：

{{< tabs defaultTab="0" type="underline" >}}

{{% tab title=":(fa-brands fa-windows fi-c-blue): Windows" %}}

页面上点 **Download for Windows**，下回来的是一个几十兆的安装包，一路 **下一步** 就能装完。

只有最后一页值得注意一下。安装向导的**选择附加任务**那一步里，有两个选项，其中一个你现在已经能够理解它是什么意思：

| 选项 | 勾上之后你会拥有 |
| --- | --- |
| **添加到 PATH** | 在任何终端里敲 `code .`，就能用 VSCode 打开当前文件夹 |

> [!tip] 融会贯通
> 你掌握了 **PATH** 的相关知识，以后再见到就能立刻理解。计算机的知识虽不算少，但重复用、常用的知识并没有那么多。

安装完成并将VSCode添加到PATH后，按下 **Win + R**，输入 `powershell` 回车，随便进一个文件夹敲：

```bash {title="在终端里执行"}
code .
```

如果 VSCode 打开了当前文件夹，说明它生效了，至于为什么生效，为什么系统能够找到`code`这个命令？你心里有数。

{{% /tab %}}

{{% tab title=":(fa-brands fa-apple fi-c-slate): macOS" %}}

把下载到的 `.zip` 解开，将 **Visual Studio Code.app** 拖进**应用程序**文件夹，就装完了 —— 没有安装向导，也没有可以勾的选项。

所以那个 `code .` 命令要**手动补上**。在 VSCode 里按 `Cmd + Shift + P` 打开命令面板，输入并运行：

```plain {title="在命令面板里搜索并执行"}
Shell Command: Install 'code' command in PATH
```

运行完，终端里的 `code .` 就和 Windows 上一样可用了。

{{% /tab %}}

{{< /tabs >}}

### 1、中文界面

VSCode 默认设置是英文。改成中文只需要装一个语言包。

点左侧活动栏最下面那个由四个小方块组成的图标（**扩展**，快捷键 `Ctrl + Shift + X`），在搜索框里输入 `Chinese`，找到 **Chinese (Simplified) (简体中文) Language Pack**，点 **安装**。装完右下角会弹出提示，点 **Change Language and Restart**，界面就是中文的了。

接着还差最关键的一个扩展。在同一个搜索框里输入 `C/C++`：

> [!warning] 认准作者是 Microsoft
> 商店里搜 `C/C++` 会出来一堆名字很像的扩展，只有作者（Publisher）写着 **Microsoft** 的那个才是官方的。

装完 **C/C++**，旁边通常还会推荐 **C/C++ Extension Pack** —— 这是微软给 C/C++ 准备的全家桶，把常用的一整套（C/C++、CMake 支持、主题等）打包在一起。**建议一次性装完**，省得以后写大一点的作业时一个个补。

> [!question] 为什么这两个扩展要分开装？
> 因为它们管的是两件不同的事：**语言包**改的是 VSCode 自己的界面文字，**C/C++ 扩展**负责看懂你的代码（语法高亮、补全、报错、以及后面要用的调试对接）。
>
> 所以VSCode本身的功能并不很多，你可以当做是一个更厉害的记事本，之所以它厉害，是因为它可以通过安装各种拓展，使其具备各种能力。

### 2、让 VSCode 记住那行编译命令：tasks.json
打开VSCode，点击左上角的 **文件**-->**打开文件夹**--> **选择你C语言代码存在的那个文件夹**
文件夹打开之后，菜单里点 **终端 → 配置默认生成任务**。VSCode 会列出一串它能找到的编译器，选带 `gcc` 的那一项：

```plain {title="选择任务时你会看到"}
C/C++: gcc.exe 生成活动文件
```

点下去，`.vscode/tasks.json` 就自动生成好了，内容大概是：

```json {title=".vscode/tasks.json", linenos=true, expand_depth=4}
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "cppbuild",
      "label": "C/C++: gcc.exe 生成活动文件",
      "command": "C:\\msys64\\ucrt64\\bin\\gcc.exe",
      "args": [
        "-fdiagnostics-color=always",
        "-g",
        "${file}",
        "-o",
        "${fileDirname}\\${fileBasenameNoExtension}.exe"
      ],
      "options": {
        "cwd": "${fileDirname}"
      },
      "problemMatcher": ["$gcc"],
      "group": {
        "kind": "build",
        "isDefault": true
      },
      "detail": "编译器: gcc"
    }
  ]
}
```


这个`tasks.json`文件**不是给你读的程序，而是一张写给 VSCode 看的「说明书」** —— 每次让它编译，它就照着这张说明书找到程序、拼好参数、执行命令。所以我们不需要背下这些东西，只要认出它回答的三个问题：**让谁去干**（`command`）、**怎么干**（`args`）、**在哪个文件夹里干**（`cwd`）。

**先看 `command`：它就是编译器在你电脑上安装的位置（上面那个路径是我的电脑上的，你自己电脑上的路径取决于你安装在了哪里）**

**再看 `args`：它就是你在上一篇文章中终端编译源文件时敲过的那串东西。**

![一位简笔人物把一张空白小卡片插进墙上的卡片架，桌上放着笔记本电脑，旁边的小机器人捧着扳手待命](images/illus-19-tasks-json.jpg "把那条命令写进一张卡片，以后只说一句「照这个来」")
`-g` 还是那个 `-g`，`-o` 还是那个 `-o`，只不过文件名换成了三个**占位符**：

| 占位符 | 它在编译那一刻会变成什么 |
| --- | --- |
| `${file}` | 你当前打开的那个源文件，也就是 `D:\code\c\hello.c` |
| `${fileDirname}` | 这个源文件所在的文件夹，也就是 `D:\code\c` |
| `${fileBasenameNoExtension}` | 去掉后缀的文件名，也就是 `hello` |

所以 `-o "${fileDirname}\\${fileBasenameNoExtension}.exe"` 展开之后，正好就是 `-o "D:\code\c\hello.exe"`。**一套任务配置，能编译这个文件夹里所有 .c 文件** —— 换成 `world.c`，它自动编出 `world.exe`。这就是占位符的价值。

把展开前后摆在一起看，你会发现它比手敲的那条命令只多了一小段：

```bash {title="两种写法，同一个结果", group="expand", name="你自己在终端手敲的"}
gcc -g hello.c -o hello
```

```bash {group="expand", name="VSCode按照tasks.json 展开后"}
gcc -fdiagnostics-color=always -g D:\code\c\hello.c -o D:\code\c\hello.exe
```

多出来的 `-fdiagnostics-color=always` 只管一件事：**让报错带颜色** —— 错误标红、警告标黄，一眼就能看出轻重缓急。不加也照样能编译，只是整屏白字看着费劲。

> [!tip] 为什么参数要「竖着写」
> `args` 里用 `[ ]` 把参数一项项排开，和横着写成一整行**完全是同一件事**：`[ "-g", "${file}" ]` 就等于 `-g ${file}`。
> 竖着写只有一个好处，但很实在 —— **以后要加参数只要加一行**，不用在长长的一行里找该往哪儿插。

> [!info] 占位符不只属于 VSCode
> `${file}`、`${fileDirname}` 这类写法你在别的地方也会遇到。(或者说你已经遇到过了，细想一下，PATH是不是就是所谓的占位符？再去翻翻你的windows环境变量设置，然后再体会一下这种占位符用法的好处。)
>
>Makefile 的变量、CMake 的 `${PROJECT_NAME}`、Shell 的 `$1` —— 本质都是同一招：**先留个空，等真正执行时再填上**。
> 现在认识它，以后在别的工具里见到就不会陌生。

**再把剩下的几个键过一遍。了解即可**

| 键 | 说人话 | 写错或删掉会怎样 |
| --- | --- | --- |
| `version` | 这份文件的**格式版本**，固定写 `"2.0.0"` | 不用改，也别删 |
| `tasks` | 任务**列表**。`[ ]` 里每写一段就是一个任务，同一个文件夹可以配好几个 | 没了它，VSCode 读不到任何任务 |
| `type` | `"cppbuild"` 表示这个任务由 **C/C++ 扩展**提供 | 这正是非认准 Microsoft 那个扩展不可的原因：装成野生扩展，VSCode 根本不认识这个类型 |
| `label` | 这个任务的**名字**，`launch.json` 靠 `preLaunchTask` 按名字引用它 | 名字对不上，按 F5 就提示找不到任务 |
| `command` | 让谁去干活 —— `gcc`。系统按 PATH 那份清单把它找出来 | 和 2.6 里 `code .` 能生效是同一套机制；PATH 里没有它就报「不是内部或外部命令」 |
| `options.cwd` | 在哪个文件夹里执行这条命令，相当于先替你 `cd` 过去 | 影响相对路径怎么解析，指到源文件所在目录最省心 |
| `problemMatcher` | 把 gcc 打印的报错**翻译成 VSCode 能标出来的条目** | 编译照样能过，但错误不能点着跳行、也没有红波浪线 |
| `group` | `"kind": "build"` 说明这是构建任务，`"isDefault": true` 说明它是**默认**那个 | 少了 `isDefault`，`Ctrl + Shift + B` 会先弹出来问你「跑哪个」 |
| `detail` | 纯备注，只给人看 | 写错不影响运行 |

> [!warning] 最容易出错的一行：label
> `"label"` 是这个任务的名字，后面 `launch.json` 会用 `preLaunchTask` 去引用它。
> 引用必须**一字不差** —— 包括空格、大小写、`C/C++` 后面的冒号，全都要对上。
> 手动改过 `label` 却忘了同步改 `launch.json`，是“按下 F5 没反应 / 提示找不到任务”这类问题的头号来源。

表里 `group` 那两个值合起来的效果就是 —— 按 `Ctrl + Shift + B` 直接跑它，不用再进任何菜单。

顺带说一句，这个文件是**跟着文件夹走的**：拷给同学、提交进 Git、换台电脑打开都能直接用。你手敲的那些命令，从这里开始不用再重敲第二遍。

**最后提醒一件小事：JSON 这个格式本身，有三个「一碰就废」的地方。**

新手改这个文件踩的坑，几乎都落在下面三条上。它们不是 VSCode 的规矩，而是 JSON 文件有格式要求：

| 事故 | 长什么样 | 为什么会废 |
| --- | --- | --- |
| **逗号** | 每两项之间要有**英文逗号**，但**最后一项后面不能有** | 多一个逗号，整个文件读不出来，任务列表直接空掉 |
| **中文标点** | 写成中文逗号、中文冒号或中文引号 | JSON 只认英文半角标点，中文标点一律让它读不懂 |
| **反斜杠** | `D:\code\c` 只写了一个 `\` | JSON 里 `\` 是转义符号，必须写成 `\\` —— 这就是 `-o` 那行为什么全是双反斜杠 |

现在回到 `hello.c`，按一下 `Ctrl + Shift + B`，下方的终端里会刷出编译信息，然后 `D:\code\c` 里多出 `hello.exe`。**板块四里手敲的那条命令，现在只需要一个快捷键。**

> [!note] Mac 读者
> Mac 上的流程完全一样（**终端 → 配置默认生成任务**），只是列表里显示的是 `C/C++: clang 生成活动文件`。
> 生成的 `tasks.json` 里改两个值就够了：`"command"` 填 `clang`，输出文件名末尾**不带 `.exe`** —— 写成 `"${fileDirname}/${fileBasenameNoExtension}"`，注意用的是正斜杠。

{{< details summary="如果终端提示 gcc 不是内部或外部命令" >}}

说明 VSCode 没能在 PATH 里找到 `gcc`。最直接的办法是把 `command` 换成完整路径：

```json
"command": "C:\\mingw64\\bin\\gcc.exe",
```

注意反斜杠**要写成两个**（`\\`）。JSON 里单个 `\` 是转义字符，`C:\mingw64` 这种写法会让 VSCode 读不懂这个文件，整个 tasks.json 直接失效。

换完之后记得把 `"detail"` 那一行也顺手改掉，它只是给人看的备注，写错了不影响运行。

{{< /details >}}

### 3、用鼠标打断点：launch.json 与 F5

编译能用了，接着把调试搬进来。打开 `hello.c`，点左侧活动栏的 **运行和调试** 图标（或菜单 **运行 → 添加配置...**），在**选择环境**里选：

```plain
C++ (GDB/LLDB)
```

再在配置列表里选 **C/C++: gcc.exe 生成和调试活动文件**。`.vscode/launch.json` 就生成了：

```json {title=".vscode/launch.json", linenos=true, expand_depth=5}
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "C/C++: gcc.exe 生成和调试活动文件",
      "type": "cppdbg",
      "request": "launch",
      "program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
      "args": [],
      "stopAtEntry": false,
      "cwd": "${fileDirname}",
      "environment": [],
      "externalConsole": false,
      "MIMode": "gdb",
      "miDebuggerPath": "C:\\mingw64\\bin\\gdb.exe",
      "setupCommands": [
        {
          "description": "为 gdb 启用整齐打印",
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
        }
      ],
      "preLaunchTask": "C/C++: gcc.exe 生成活动文件"
    }
  ]
}
```

这个文件里有五个字段需要重点关注：

| 字段 | 它说的是什么 |
| --- | --- |
| `program` | 要调试的是**可执行文件** `hello.exe`，不是源文件 `hello.c`。调试器只能操作真正能跑起来的东西 —— 这里写成 `.c` 是新手最常见的错 |
| `miDebuggerPath` | 用哪个调试器。就是跟着 MinGW 一起装进来的那个 `gdb.exe` |
| `preLaunchTask` | 按 F5 之前，先跑哪个任务。它的值必须和 `label` **完全一致** |
| `externalConsole` | 是否另开一个黑框窗口。`false` 表示就在 VSCode 下方的终端里跑，看着更清楚 |
| `stopAtEntry` | 是否在 `main` 的第一行就停下来。`false` 表示不停，直接跑到你的断点 |

> [!note] Mac 读者：这个文件里有三处不同
> 生成配置时同样选 `C++ (GDB/LLDB)`，但填的值不一样：`"MIMode"` 用 `"lldb"`、`"miDebuggerPath"` 用 `"/usr/bin/lldb"`、`"program"` 去掉 `.exe` 后缀（写成 `"${fileDirname}/${fileBasenameNoExtension}"`）。`preLaunchTask` 则要填 clang 任务的 `label`。

![一条虚线小路上立着路标，一个简笔人物站在路标旁低头看手里的小本子](images/illus-20-breakpoint.jpg "在关键的那一行，按一次暂停")

| 快捷键 | 干什么 |
| --- | --- |
| `F5` | 继续运行，直到下一个断点或程序结束 |
| `F10` | 单步跳过：执行下一行，不钻进函数里面 |
| `F11` | 单步进入：如果下一行是函数调用，就钻进去 |
| `Shift + F5` | 停止调试，强制结束程序 |

> [!tip] 这就是 gdb 的图形化版本
> 你不是换了一套工具，你用的是**同一个 `gdb`**：`program` 里的 `hello.exe` 是它启动的程序，`miDebuggerPath` 里的 `gdb.exe` 是它本体，点红点就是帮你敲了 `break`，按 `F5` 就是帮你敲了 `run`，按 `F10` 就是 `next`，看变量面板就是帮你敲了 `print`。
> 唯一的区别：**命令它替你敲了，眼睛替你省了。**

### 4、Code Runner：方便的代价

在扩展面板里还能找到另一个很受欢迎的扩展：**Code Runner**。装上之后，编辑器右上角会多出一个 :(fa-solid fa-play fi-c-orange): 按钮，点一下就能跑，快捷键是 `Ctrl + Alt + N`。

听上去比 `Ctrl + Shift + B` 还省事。但它的默认行为里藏着两个坑 —— 把它的默认命令展开看：

```plain {title="Code Runner 对 .c 文件默认执行的命令"}
cd $dir && gcc $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt
```

> [!warning] 
> **① 没有 `-g`。** 这条命令编译出来的程序里**不含调试信息** —— 上篇文章说过，没有 `-g`，断点就是一句空话。所以用 Code Runner 跑出来的 `hello.exe`，`F5` 是调不动的。
> **② 不管编码。** 它也不会替你加 `-fexec-charset=GBK`，程序里的中文照样可能变成乱码。

所以结论很清楚：**Code Runner 适合“我就想快速试两行”，不适合正经写代码。** 写作业、做课程设计，老老实实用 `Ctrl + Shift + B` 加 `F5` —— 那套配置里既有 `-g`，又能打断点，还能一眼看到变量。

### 5、环境体检表

到这一步，整套环境已经齐了。回头看一眼你走过的路：

![手工流程与 VSCode 的对应关系：手敲 gcc 命令对应 Ctrl+Shift+B，手敲 gdb 命令对应点断点按 F5，记住路径参数对应写进 .vscode 里两个配置文件](images/illus-21-vscode-does.svg "VSCode 究竟替你做了什么")

最后按下面这张表逐项验一遍。**全部打勾，说明你的环境没有任何问题**，可以放心开始写代码了：

| # | 验收项 | 怎么做 | 期望结果 |
| --- | --- | --- | --- |
| 1 | 编译器就位 | 终端里敲 `gcc --version` | 打印出版本号 |
| 2 | 调试器就位 | 终端里敲 `gdb --version` | 打印出版本号 |
| 3 | VSCode 接进了终端 | 在 `D:\code\c` 里敲 `code .` | VSCode 打开这个文件夹 |
| 4 | 项目能识别 | 打开文件夹后，菜单里有 **终端 → 配置默认生成任务** | 列表里出现 `C/C++: gcc.exe 生成活动文件` |
| 5 | 一键编译 | 打开 `hello.c`，按 `Ctrl + Shift + B` | 终端刷出编译信息，`D:\code\c` 里出现 `hello.exe` |
| 6 | 程序跑得对 | 在 `D:\code\c` 里运行 `.\hello` | 输出 `"Hello, C!"` |
| 7 | 断点能用 | 在 `printf("Hello, C!");` 行号左边点一下，按 `F5` | 程序停在这一行 |

> [!success] 到这里，你已经是一个能干活的人了
> 会安装编译器、会配置PATH让系统找得到安装的软件、会用终端命令计算机执行程序、通过配置文件让编辑器记住了你的命令从而实现一定程度的自动化、调试断点点一下就生效。
> 剩下的事只有一件 —— **开始写代码。**

---

## 结语 · 环境配好之后，该往哪走

> [!abstract] 你真正拿到手的东西
> 不是“会装 VSCode”，而是**知道每一步在发生什么**。
> 别人按那个小三角按钮的时候只知道“跑起来了”；你知道那背后是 `gcc` 在做预处理、编译、汇编、链接，知道 `-g` 决定断点能不能生效，知道 PATH 是系统手里的那份通讯录，知道 VSCode 只是把你敲过的命令记进了两个 JSON 文件。
> 环境配置会随版本变化，**这套原理不会。**

接下来该往哪走？

1. **不要急着换 IDE。** 你以后一定会听到 VS / CLion / Vim 的名字，也可能被人劝“换一个更好用”。但是 —— 工具切换是几分钟的事，看懂编译、链接、调试这三件事才是值钱的。熟知原理才能以不变应万变，换任何工具都只是熟悉一下界面，况且VSCode大多数情况下已经足够好用。
2. **先把命令行用熟。** `-o` 定输出名、`-g` 带调试信息、`-Wall` 开警告、`-std=` 定标准 —— 这四个参数够你用很久。更重要的是：**服务器上不会有按钮给你点**，命令行是你唯一的操作方式。
3. **把调试器用熟。** 断点、单步、查看变量，这三件套能解决你未来 80% 的“为什么结果不对”。学会先怀疑自己，再让程序停下来告诉你答案，而不是盯着代码发呆。
4. **下一步学 Makefile，再进阶 CMake。** 现在你只有一个 `hello.c`，手敲一条命令当然没问题。等当你需要编写三个 `.c` 文件互相调用的时候，你就该让机器替你记住“先编哪个、再链哪个”了 —— 这正是 Makefile 在干的事。
5. **遇到报错，原样复制去搜。** 不要用自己话转述，报错原文里的文件名、行号、错误码都是关键词。另外养成一个习惯：**永远先看第一条 error**，后面的错误常常是它的连锁反应。
6. **动手才是真的学。** 看十遍教程不如自己写一遍。可以从洛谷、力扣、牛客上找最简单的入门题开始，也可以把课本上的例题自己敲一遍再改几个数字试试。**卡住的时候先别急着问，先自己调一次断点。**
7. **现在就可以学 Git。** 不用等写大项目。哪怕只有一个 `hello.c`，也可以建一个仓库 —— 往后你会发现“能随时回到昨天那个能跑的版本”是一件多让人安心的事。

一张图看清接下来这几步的关系：

```mermaid
graph LR
  A["本文完成<br/>环境全部跑通"] --> B["刷基础题<br/>洛谷 / 力扣 / 牛客"]
  B --> C["读教材<br/>《C Primer Plus》"]
  C --> D["学 Git<br/>把代码管起来"]
  D --> E["学 Makefile<br/>多文件自动构建"]
  E --> F["进阶 CMake<br/>工程级项目"]
```

顺序不必严格照搬 —— 先学 Git 再刷题也完全可以。但有一点值得记住：**每一步都踩在已经跑通的环境上往前半步**，而不是推倒重来。

{{< center-quote >}}
**祝你的第一个程序顺利跑通。** 从今天起，我希望读者能够记住从小开始就耳熟能详的那句话：“**基础不牢，地动山摇**”。
{{< /center-quote >}}


---

> 作者: [青梧](https://zzzheng.com)  
> URL: https://zzzheng.com/posts/a4be98c/  

