# 二、用配置好的VSCode实战做一道算法题


<!--more-->
## 多说无益，直接开干
以这道题为例
{{< link href="https://www.luogu.com.cn/problem/P5720?contestId=337697" content="P5720 【深基4.例4】一尺之棰" title="一道用来练手的算法题" card=true card-icon="fa-solid fa-square-binary" >}}

## P5720 【深基4.例4】一尺之棰

### 题目描述

《庄子》中说到，“一尺之棰，日取其半，万世不竭”。第一天有一根长度为 $a$ 的木棍，从第二天开始，每天都要将这根木棍锯掉一半（每次除 $2$，向下取整）。第几天的时候木棍的长度会变为 $1$？

### 输入格式

输入一个正整数 $a$，表示木棍长度。

### 输出格式

输出一个正整数，表示要第几天的时候木棍长度会变为 $1$。

### 输入输出样例 #1

#### 输入 #1

```
100
```

#### 输出 #1

```
7
```

### 说明/提示

数据保证，$1 \le a\le 10^9$。
## 0. 建立基本工作空间，打开VSCode，新建源文件
![在桌面右键VSCode图标，打开VSCode](images/open-vscode.png "打开VSCode")
![在VSCode中打开文件夹](images/open-folderpng.png "在VSCode中打开文件夹")
> [!note] 随便打开一个空文件夹即可，这个文件夹就是我们写这道算法题的地方

![新建main.c源文件](images/create-main.c.png "新建main.c文件")
## 1. 自信编码，谁都不爱
> [!info]然后根据题目要求，享受你的编码过程，你会发现，你有语法错误，拓展会帮你纠正，写`scanf`等函数调用时，拓展会有补全和参数信息提示，虽然在AI盛行的当下，这算古法编程，但这也确实是科班出身的必经之路。

![开始写算法代码](images/enjoy-code.png "享受你的编码过程")
> [!info]然后凭着直觉写下这道题的解法，自信满满地提交。

![第一印象](images/first-mind.png "第一印象写法")
![WA](images/WA.png "Wrong Answer")
> [!failure] 我靠，劳资居然也会错？

> [!tip]接下来，终于轮到我们配置好的VSCode出场了，让我们通过一步步调试来看看，我们第一印象的这种解法错在哪里？为什么不对？
## 2. 开始调试自己的程序
### 2.1 在循环处打断点

![打断点](images/breakpoint.png "打断点")

左边那个小红点就是**断点**：程序跑到这一行会停下来等你。点一下行号左侧的空白就能打上，再点一下取消。

### 2.2 把程序送进调试器

点右上角那个**向下的小箭头**（就在「运行」图标旁边），会弹出一个菜单，里面有两项：

![点击调试按钮](images/create-json01.png "点这个小箭头，然后选「调试 C/C++ 文件」")

选下面那一项 —— **调试 C/C++ 文件**。VSCode 会先让你**选一个调试配置**，它已经自动扫到了你本机的 gcc：

![选调试配置](images/create-json02.png "选 C/C++: gcc.exe 构建和调试活动文件")

点下去，程序就跑起来了。

> [!warning] 这里有个所有人的第一反应都会踩的坑
> **你并没有生成 `launch.json`。**
>
> ![进入调试界面](images/debug-button.png "标题栏写着「没有配置」，程序却已经在跑了")
>
> 没有配置文件，程序怎么也跑起来了？往下看，这件事正是这一节最该弄明白的地方。

### 2.3 喂给它数据，然后看它停在哪

程序起来之后不会自己动，它在**等你输入**。点一下下方终端，把样例里那个 `100` 敲进去，回车：

> [!info]输完之后，左侧多了些东西
> 你会发现左侧面板里冒出了 `变量`、`监视`、`调用堆栈`、`断点` 四栏，而且程序很明显停在了 `while` 循环那一行 —— 那一行整个被高亮成了黄色。**这就是断点生效的样子。**

![调试中](images/debuging.png "调试自己的程序，观察变量值")

### 2.4 单步：让它一次只走一行

现在点工具条上那个**拐着弯的箭头**（悬停显示「单步调试 (F11)」），代码就会往下走一行：

> [!info]代码一行一行地执行，变量一格一格地变
> 每点一次，左边的 `length` 和 `day` 就跟着变一次。自己试着多点几下，盯着那两个数看 —— 这就是「变量在程序里怎么变」这件事第一次变得**肉眼可见**。

![调试中](images/debuging01.png "调试自己的程序，观察变量值")

### 2.5 找到问题：`day` 是 6，不是 7

一直点到循环跳出去，停在了 `printf` 那一行。这时候看左边的 `变量` 栏：

| 变量 | 值 | 你以为的 |
| --- | --- | --- |
| `length` | `1` | `1` ✅ |
| `day` | **`6`** | `7` ❌ |

![找到原因](images/find.png "找到问题所在：day 停在了 6")

**循环确实跑了 6 次，但 `day` 是从 `0` 开始数的。** 从第一天算起一共 7 天，程序只数出来 6 —— 答案当然是错的。

找到原因就好办了。先让它继续跑完，退出调试：

![放程序继续执行](images/continue.png "按「继续」，让程序跑完退出")

### 2.6 回头看看：`launch.json` 去哪了？

先把这件事说透 —— 因为它是这一整节里最容易被误解的地方。

上一篇里，你亲手生成了一份 `launch.json`，还逐个字段读过。所以到了这一篇，你多半会以为刚才那一路调试，是 VSCode **读了那份配置**才跑起来的。

**不是。** 这次它连文件都没生成，你真去 `.vscode/` 目录里翻，只有 `tasks.json`。调试面板标题栏写的也是「没有配置」。

原因在 **C/C++ 扩展**身上：它内置了一份**兜底配置**。当它发现你没有 `launch.json`、或者配置列表是空的时候，它不等你配置，直接按自己的默认值把调试器拉起来 —— 就用当前打开的那个源文件。这就是菜单里那句「调试 C/C++ 文件」的含义：**不用你配置，我来处理这一份文件。**

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

{{% tab title=":(fa-solid fa-bolt fi-c-green): 直接调试（默认兜底）" %}}

- **触发方式**：`运行` → `调试 C/C++ 文件`
- **它用谁**：当前**正在编辑的那个源文件**，`${file}`
- **`launch.json`**：不生成、不存在，标题栏显示「没有配置」
- **编译**：先按 `tasks.json` 里 `group.isDefault` 为 `true` 的那个任务编一遍
- **适合**：随手试一段代码、跟教程走一遍。**快，但什么都留不下**

{{% /tab %}}

{{% tab title=":(fa-solid fa-gear fi-c-blue): 用 launch.json" %}}

- **触发方式**：`F5`，或标题栏那个绿色三角
- **它用谁**：`launch.json` 里 `program` 字段写死的那张路径
- **`launch.json`**：必须是现成的，且能对上一个 `configurations` 条目
- **编译**：按配置里的 `preLaunchTask` 去执行对应任务，**名字对不上就报错**
- **适合**：一个项目反复调试。**慢一点，但配置能跟着文件夹走，换台电脑打开就是同一套**

{{% /tab %}}

{{< /tabs >}}

> [!important] 一句话记住它们的区别
> **「调试 C/C++ 文件」是「帮我调这一个文件」；`launch.json` 是「我规定好以后都这么调」。**
> 前者省事，后者省心 —— 你现在写的这点代码用哪个都行，但等你手里有一个正经项目，那就必须得上配置了。

那 `launch.json` 到底怎么来？**在运行和调试面板里点「创建 launch.json 文件」**，VSCode 会把上面那份兜底配置**原样写成一份文件交给你**，之后你就可以在上面逐字改了。

### 2.7 改一行，再验一遍

错误原因已经清楚了：**循环里数的是「第二天往后」，第一天没人数。** 所以在循环外面补一句 `day++`：

![修正程序](images/correct.png "改正代码逻辑：把 day++ 补在循环外")

存盘，按 `F5` 再走一遍。这次 `day` 停在 `7`，符合预期 —— 提交给洛谷试试：

![AC](images/AC.png "答案正确，AC！")

---
## 冷静一下：刚才到底发生了什么

代码跑完了，`AC` 也拿到了。但在关掉这篇文章之前，回头看一眼刚才那一小段路 —— 你会发现它和你以为的不太一样。

第一版代码，VSCode **一个字都没报**。没有红线，没有波浪线，`Ctrl + Shift + B` 编译得干干净净。编辑器认可它，编译器认可它，只有洛谷那一排绿色的 `AC` 不认可它。

**因为你的程序语法完全正确，只是逻辑错了。**

这是初学者最容易卡住的一个坎，也是本文最想让你记住的一件事：

| 出错类型 | 谁会发现 | 你看到什么 | 怎么修 |
| --- | --- | --- | --- |
| **语法错误** | 编辑器 / 编译器 | 红线、波浪线、终端里一串 `error:` | 顺着报错改就行，最省事 |
| **逻辑错误** | **只有你自己** | 什么都没报，结果是错的 | 只能靠调试，靠盯着变量一步一步看 |

顺手复习一遍编译器的四个参数：

| 参数 | 它替你做了什么 | 少了会怎样 |
| --- | --- | --- |
| `-fdiagnostics-color=always` | 让报错带颜色 | 一整屏白字，读起来费劲 |
| `-g` | 往可执行文件里塞进行号、变量名 | **断点是灰色的，点不动** —— 没有调试信息，gdb 只能看到一堆内存地址 |
| `-Wall` | 打开所有常用警告 | 一些明显的可疑代码（比如赋值写成比较）它不会再提醒你 |
| `-O2` | 开优化，让程序跑得更快 | 你反而不该要它：**优化会打乱代码和行号的对应关系**，调试时看到的位置会对不上 |

> [!tip] 顺便说一件你可能没注意的小事
> 上面调试截图里的命令长这样：
> ```plain
> ${env:DEBUGINFOD_URLS}=''; & 'C:\Users\Soleaf\.vscode\extensions\ms-vscode.cpptools-1.34.4-win32-x64\debugAdapters\bin\WindowsDebugLauncher.exe' ...
> ```
> 一长串，看着就头大。但你把参数一个个挑出来读，会发现每个参数都能对上一样东西：`--stdin=` 对应你敲进去的 `100` 该往哪儿送，`--debugExe=` 后面跟的就是 `gdb.exe`，`--interpreter=mi` 表示它用的是机器接口。
> 而你**什么都没配置**，它却知道该送哪个文件、该调哪个 gdb —— 因为你点的那一下「调试 C/C++ 文件」，**就是这份兜底配置的开关**。
> 前两篇文章讲的那个规律，在这一行里又出现了一次：**图形界面不是魔法，它只是把你的配置翻译成了一条很长的命令，然后替你敲下去。** 只不过这一次，那份「配置」是它自带的。

## 该轮到你上手了

把 `100` 换成这几个数，重新跑一遍，看看 `day` 也跟着变：

| 你输入 | 正确答案 | 你的程序输出 |
| --- | --- | --- |
| `1` | 1 | ？ |
| `100` | 7 | ？（就是你刚才那个） |
| `2` | 2 | ？ |
| `1000000000` | 30 | ？ |

第二行你已经验证过了。**第 1 行和第 3 行是专门给你挖的坑** —— 想想为什么输入 `1` 的时候答案应该是 `1` 而不是 `0`。

顺手再改一个地方：`while (length != 1)` 这行里，如果长度本来就小于 1 会怎样？把 `int length` 换成 `float` 试试？这些改动都不需要重学任何东西 —— 你已经有了工具，改完按 `F5`，看变量，就这么简单。

{{< details summary="实在想不出来，点开看提示" >}}

`while` 的条件是 `length != 1`，只要长度不等于 1 就继续循环。

输入 `1` 的时候，条件从一开始就是假 —— **循环一次都不会执行**，`day` 还是初始的 `0`，程序直接打印 `0`。但题目说得很清楚：第一天木棍长度就是 1，答案应该是 `1`。

这就是那个 `day++` 为什么必须补在循环外面 —— 它管的是**第一天**，循环里那次自增管的是**第二天往后**。两件事，别混。

{{< /details >}}

## 写在最后

> [!abstract] 你真正拿到手的东西
> 不是「学会了一道洛谷题」。这道题你明天就会忘掉。
>
> 你真正拿到手的是：**代码跑不对的时候，你知道该往哪儿看。**
> 你可以让程序停在任何一行，看着 `length` 和 `day` 一格一格地变，亲手确认是哪一步跟你想的不一样。你不再需要盯着代码发呆、猜哪里错了，也不再需要到处插 `printf` 然后一条条删掉。

走到这里，一条完整的链路已经握在你手里了：

```mermaid
graph LR
  A["〇 · 认识编译器<br/>装 MinGW、配 PATH"] --> B["一 · 搬进 VSCode<br/>tasks.json + launch.json"]
  B --> C["二 · 实战一道题<br/>写代码 → WA → 调试找bug → AC"]
```

- 第〇篇解决的是「**程序是怎么跑起来的**」；
- 第一篇解决的是「**怎么让工具替我跑**」；
- 这一篇解决的是「**跑错了怎么办**」。

三件事凑齐，你就不再是那个「照着步骤抄一遍、跑出 Hello World 就不知道下一步该干嘛」的人了。**你是一个能自己找出问题在哪的人** —— 这是往后的所有语言、所有框架、所有项目都绕不开的底子。

{{< center-quote >}}
**基础不牢，地动山摇。**
别急着追新技术。你刚才用的这些东西 —— 编译器、断点、单步、看变量 永远都有用，相信我，这套流程会贯穿你计算机学习的整个生涯。
{{< /center-quote >}}

## 参考文献

题目和工具链的官方来源，需要查证细节时来这里：

1. [P5720 【深基4.例4】一尺之棰 · 洛谷](https://www.luogu.com.cn/problem/P5720) —— 本文使用的那道题，题面和数据范围以这里为准。
2. [Debug C++ in Visual Studio Code](https://code.visualstudio.com/docs/cpp/cpp-debug) —— 微软官方调试文档：断点、单步、变量面板、调用堆栈的完整说明，`launch.json` 里每个字段的官方定义。
3. [Configure VS Code for Microsoft C++](https://code.visualstudio.com/docs/cpp/config-msvc) / [Configure VS Code for GCC](https://code.visualstudio.com/docs/cpp/config-mingw) —— `tasks.json` 与 `launch.json` 的生成流程，含 MinGW 下的完整示例。
4. [Debugging in Visual Studio Code](https://code.visualstudio.com/docs/debugtest/debugging) —— 侧栏「运行和调试」面板本身的说明：什么是调试配置、为什么标题栏会显示「没有配置」、以及「创建 launch.json 文件」按钮会写出什么。**本文 2.6 节那个坑，答案在这一页。**
5. [GDB Documentation](https://sourceware.org/gdb/documentation/) —— 想搞清楚 VSCode 那个调试按钮背后究竟敲了什么，答案在这里。
6. [GCC online documentation](https://gcc.gnu.org/onlinedocs/) —— `-g`、`-Wall`、`-O2`、`-fdiagnostics-color` 这些参数的权威解释。

---

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

