0. 目标 、视频、准备、FAQ
0.1 本课目标
上节课,我们学习了 vscode 的 “配置(Profiles)”,明白了通过为不同的工作目的创建不同的配置,不仅可有效避免 vscode 变臃肿,更重要的是,能让我们的每一样工作都能在 vscode 上获得更好的操作体验,更高的工作效率。
从官网了解更多 vscode 配置的信息: Profiles in Visual Studio Code
接下来三节课,我们为 C/C++ 开发,创建两套配置。
- 本节课:创建“CS-CPP-Simple”配置,实现编译单一C/C++源文件的项目;
- 下节课:基于本课配置改进,实现可编译多个源文件以及集成复杂的第三方库;
- 下下节课:创建 “CC-CPP-CMake” 配置,实现工业级的C++项目构建。
0.2 视频
建议先看一番不到10分钟的视频,再细过文字教程。
0.3 预备干净环境
随便打开一个 Windows 的控制台,当然,不能是前两节课我们挂接在 Windows 终端的 MSys2,保险方法是:热键:Win + R ,输入 cmd.exe,回车。然后输入 g++.exe ,你应该看到如下输出:
'g++' 不是内部或外外部命令,也不是可运行的程序或批处理文件。
如果你看到的是 g++ 程序的输出,说明你的系统路径包含某个 g++.exe 的全局路径,对于纯学习而言,这能带来一定的方便,但本课程名称中的“保姆级”,乃培育真实的C/C++软件研发工程师之意。在复杂工作任务中,电脑上存在全局可直接执行的 g++.exe 非常糟糕——除非你的工作也非常简单——,所以强烈建议取消,方法就是打开Windows系统信息,进入“高级系统设置”,再进入 “环境变量”,先编辑用户级别的“Path”变量 ,如果发现之前有 g++.exe 所在路径,可在路径前面加上 – 以取消它,如下图:
确定后,再用同样的方法检查系统变量。
如果你对以上操作不太熟悉,可以先不处理。本课稍后会有此项操作过程的录屏视频演示,到时看明白了再来操作也不迟。
0.4 FAQ
Q1:都是用 C/C++编程,为什么需要两套配置?
一套 《白话 C++ 之 练功》 ,或者一本《C++ Primer》 学下来,你可能需要写上百个 C/C++ 代码,并且代码基本是“日抛型”,学明白了,就马上要再创建下一个,在这类情况下,我们会更希望专注于学习本身,如果创建一个项目就要费上大半天,并且还会在磁盘上生成一堆文件),显然影响效率。
工作中,或者学习到一定水平后,我们要面对复杂的项目构建过程,典型如:
- 需要和不少第三方库打交道;
- 项目的构建过程有很多的选项,比如一个项目需要生成多个二进制文件等。
这时候,使用简单方案无法胜任,我们需要引入成熟的构建管理工具。
我们为以上两种目的,分别创建一套VSCODE的专用配置,并且第一套配置可平滑升级到第二套。
Q2:为什么不合成一个配置?
技术上当然可以——事实上这种方法网上到处是,包括 VSCODE 官方文档。但需要用到的两个主要扩展配合得不好,一套项目有两种编译机制,并且在界面上将并存两套操作入口,加载慢、不清爽;有些入口已经失效但还留在界面上,有些入口一点去,就是一个误操作……
Q3:多套配置会造成相同扩展被安装多次吗?
不会,比如,我们在 A 配置下安装 K 扩展,然后大 B 配置下又安装 K 扩展,VSCODE 会很聪明地实质只在你的磁盘上安装一个 K 扩展,但可以实现同一个扩展在A配置、B配置下,各有各的设置,我们正是利用这个机制,在第二配置中,去掉不必要的UI入口。
1. 创建 C/C++ 简易配置
打开 VSCODE,如果它自打开之前的文件夹,通过主菜单 “文件 → 关闭文件夹” 关掉。然后通过主菜单 “文件 → 首选项 → 配置”,或者通过左侧工具栏,进入配置页面,如下图:
为教学效果更清晰,我已删除之前创建的配置,只留下 VSCODE 自带的默认配置。
建议将默认配置用于日常编辑 TXT、JSON、MARKDOWN 等文本,只安装此类工作相关的,通常比较轻理的扩展;上图表明,我现在的默认配置,只安装了简体中文包扩展。
点击 “新建配置文件”,然后:
- 填写名称为 “CS-CPP-Simple”;
- 选择你喜欢的图标,建议就默认的小齿轮;
- 确保所有 “内容源” 都是 “无”;
- 点击页面右下的 “创建” 按钮。
2. 切换配置并安装扩展包
2.1 切换到 CS-CPP-Simple 配置
首先,在配置页的配置列表中,将 “活动” 配置,切换为刚刚创建的 “CS-CPP-Simple” (方法就是点击名称后面的打勾)—— 这一步很重要哦!
接着,使用 Ctrl + K, T,为你日常写简单的 C/C++ 程序,选一套主题。上节课我用的 蓝色主题,被学医的同学嘲笑了……算了,我就使用VSCODE安装自带 “现代深色”吧……
我们后面的多数操作,就在该配置下进行,并且多数明面上的操作结果只在该配置下发挥作用。
2.2 安装“纯” C/C++ 扩展
Ctrl + Shift + X,打开扩展商店面板,在过滤栏输入 C/C++ (纯英文字母或字符),稍等 片刻:
注意区别图中三个图标一样的扩展:
- C/C++:用于编写 C/C++ 代码时的智能辅助,以及配置外部编译器、调试器,实 C/C++ 代码的编译、调试、运行。这是我们现在需要的。
- C/C++ Themes:不用安装。这是官方早先特意为 C/C++ 实现的可对代码进入带语义的颜色渲染,后来普通主题也支持了……
- C/C++ Extension Pack。 这是一个大一统包,包含了前两者以及CMake等更多扩展,现在严禁安装!。
为避免后续在 “CS-CPP-Simple”配置下写 C/C++代码时,VSCODE 疯狂地推荐它,我们需要关闭 “C/C++ Extension Pack”的推荐选项。
① 在左侧列表中选中它,② 右侧扩展内容页点 “自动更新” 后面的小齿轮按钮,选择 “忽略建议”。
结论就是:日常写简单的 C/C++ 代码,我们只需要图中第一项全称为 “C/C++ for Visual Studio Code” 的扩展。请现在该项的 “安装” 按钮以完成安装。
切换到“配置文件”页,确保正确的扩展被安装到正确的配置下。
3 初识 VSCODE 设置
首先,请记住这个快捷键:“Ctrl + ,” (即:Ctrl 键加英文逗号),通过它,或者主菜单:文件 → 首选项 → 设置(Settings),进入(超级复杂的)设置页面。
在每个配置(Profiles)下,VSCODE的设置(Settings)又区分为用户(User)和工作区(也称工作空间)两级。同一用户打开的所有工作区,都默认启用用户级别的设置,除非用户在当前工作区中,采用了新的配置(称为:工作区设置项覆盖用户级设置项)。
☛ 重点理解:默认级 Vs. 用户级 Vs. 工作区级
- 用户级别的设置作用范围大。一项设置,在用户级别设置合理,则多数工作区无需再配置;如果设置不合理,则几乎每新建一个工作区,你都需要刻意“调正”该项设置;显然,用户级别的设置,要谨慎。
- 工作区级别的设置优先级大。同一个设置项,工作区级的设置会覆盖用户级的值;通常,如果小项目也要在工作区做一堆设置,难忍;大项目因为用得久,所以可以忍;
- 出厂设置往往是兜底的值。事实上,还有一个更底层的级别:出厂设置。绝大多数设置项,都有来自VSCODE或扩展自身的出厂值;当既示做工作区和用户都未对某项设置修改时,使用的就是出厂默认值(${default}),大多数情况下,出厂默认值就是合适的那一个;
注:如某个设置项通用所有配置,它会在设置标题后面加上 “(适用所有配置文件)”。
由于此刻我们的VSCODE没有打开任何文件夹,所以,在设置里,只会看到用户级别,如图:
点击左侧目录树中的“扩展”……并非只有 C/C++ 扩展,而是哗的出来一大串,那是因为 VSCODE 有预安装的,也不让卸载(也许可以?反正我没找到方法)的扩展,也是在默认配置下就有的扩展——正好看看哪些对我们有用:
- 写网页:CSS + HTML + TypeScript + JavaScript(带调试)+ Emmet;
- 写 JS 或 TS 的项目(毕竟这是VSCODE的根基):Npm、Grunt、Gulp、Jake……
- 写 Markdown ,包括用它写 数学公式(Markdown Math);
- 预览图片等(Media Previewer);
- 编辑 JSON (VSCODE的设置,就是使用JSON格式保存);
- 源代码版本管理:Git、GitHub,所以,你很方便把相关代码保存到 GitHub(如果网络通畅的话);
- 编程语言界曾经的杠把子:PHP,VSCODE 给了它足够的尊严。
4. 设置 C/C++ 扩展(用户级)
重要提醒: : 接下来我们的每项设置,都将作用在用户级别范围,因此我们只会选择有充分必要的设置项;哪怕如此,请依然保持小心,记住一个原则:尽量不要污染用户级别的设置。
4.1 了解 C/C++ 扩展包含内容
在设置页面左侧的目录树,选择并打开 “扩展” 节点,,再选择 “C/C++” 节点将看到:
本课我们只需修改 “IntelliSense(智能感知)”,“IntelliSense” 我常称它为 “智能助手”,官方介绍如下:
IntelliSense 是 VS Code 中内置工具,提供各种代码编辑功能,帮助您更快、更高效地编码。例如,代码完成、参数信息、语法突出显示、代码操作和成员列表需使用 IntelliSense 生成。
4.2 设置 C/C++ 基础信息
4.2.1 设置语言标准
先来告诉“智能小助手(IntelliSense)”,用哪一个片的语言标准,来理解、辅助、引导我们写代码C/C++代码。
注意:这只是告诉“助手”我们默认使用的语言版本(免得以后每写一个程序就要手改一次),并不是后面我们写程序时,真正使用的语言版本。
第2学堂的C/C++相关课程,使用C/C++17标准最多(这也基本是当下最成熟的版本)。
在设置页面的过滤栏输入 “Cpp Standard”,然后将C++和C的语言标准,都设置成 17 ,如图:
如果因为VSCODE升级或其他原因,造成你的过滤结果和上图略不同,可以考虑输入更精确的关键词:“C_Cpp Default Standard”。
4.2.2 设置编译器标准
同样,接下来的操作,只是要告诉“助手”,我们的C/C++程序默认使用的哪个编译器,而非限定我们以后写的程序,都只能使用这个编译器(当然,就学习而言,我们基本就是使用这个编译器)。
这回,在设置页面的过滤栏输入“Cpp Compile Path”(或 c_cpp.default.CompilerPat),结果如图:
明明就是一行输入的事,不懂 VSCODE (或C/C++扩展)为什么要设计成需要打开文件来修改……请点击图中链接 “在 Settings.json编辑”,将打开一小段文件:
{
"C_Cpp.default.compilerPath": "",
"C_Cpp.default.cppStandard": "c++17",
"C_Cpp.default.cStandard": "c17"
}
嗯?原来语言标准也在这里啊?(感觉前面白做了)。请在 “compilerPath”这项,填写我们在前面课堂里安装的 msys2 特定环境的 g++ 编译器的完整路径,因为是直接修改 JSON 数据,所以请注意路径分隔符需要加转义符 \
,变成 \\
。在我这边,填值后,该行将是:
"C_Cpp.default.compilerPath": "C:\\msys64\\ucrt64\\bin\\g++.exe",
我们主要使用C++,且C++基本兼容C,所以填写的编译器是 g++.exe,如果你是纯粹的 C 程序员,此处可改成 gcc.exe。
Ctrl + S 保存这个文件,然后关闭它;再把关闭整个设置页……我们要开始在 vscode 写C++代码了。
5 第一个 C++ 项目——简单配置
先在你的电脑上合适的位置,准备一个空文件夹,取名 “HelloVSCCppSimple”。然后用 VSCode 打开这个文件夹。
打开后,VSCODE 交“闪回”到默认配置,所以,请将它切换到前面 “CS-CPP-Simple”配置(后续课堂会教大家如何为 “切换配置” 设置一个热键)。
5.1 新建 CPP 文件
在 helloVSCode.cpp 中,输入以下代码,并保存。
#include <iostream>
using namespace std;
auto main() -> int
{
cout << "Hello VSCode." << endl;
}
检查文件页的右下角是不是内容如下:
- 空格 4: 使用四个空格缩进代码;
- UTF-8:「重点」我们默认使用 C/C++界最主流的源文件编码方案:UTF-8;
- CRLF:如有 Windows + Linux 双平台编程需求,可改为 LF;
- Win32: C/C++ 扩展 “智能助手” 将基于 Windows 平台提供功能;
至此,我们仍然只是将 VSCODE 当成一个编辑器在使用,下面我们要为当前项目生成一个调试配置(“Debug Configuration”)。
☕ 怎么翻译 “Configuration”?
VSCODE中文简体语言包把 Profiles 翻译成 “配置文件”(但实际这里有“文件”什么事?),把 Setting 翻译成设置,然后再遇上 “Configuration”,它直接放弃翻译了……我考虑了很久,似乎只能屈服于复用“配置”一词,如果你有更好的翻译,麻烦告诉我……
5.2 生成调试配置 (Debug Configuration)
接着,眼球顺时针旋转 315,看向1点钟方向,检查文件页的右上角是不是多出如下入口:
点击其中的 “小齿轮”,VSCODE将显示在本机上检测到的C/C++编译器:
这里会有两种变化,一种是检测到多个编译器,说明你的电脑上安装多个C/C++编译器,评估仔细选择课程所用的 MSys3-UCRT64(或你之前的选择安装的环境,比如 MINGW64)。另一种可能是一个编译器也没检测到,这时尝试如下操作:
- 在 VSCODE 中按下 Ctrl + Shift + P,然后输入 C++Select IntelliSense Configuration;
- 选择 “在我的计算机上选择另一个编译器…”
- 找到之前的安装的 msys2-ucrt64 的 g++.exe……
- 再点击前述的 “小齿轮” ……
接上图,选择 “C/C++: g++.exe 生成和调试活动文件……” 选项,VSCODE 将在工作区的生成 .vscode 子目录,并于其下至少生成两个文件:tasks.json 和 launch.json,并且会自动打开后者,如图:
5.2.1 详解 tasks.json
请打开 tasks.json,我们从“任务”说起。
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: g++.exe 生成活动文件",
"command": "C:\\msys64\\ucrt64\\bin\\g++.exe",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe"
],
"options": {
"cwd": "C:\\msys64\\ucrt64\\bin"
},
"problemMatcher": [
"$gcc"
],
"group": "build",
"detail": "调试器生成的任务。"
}
],
"version": "2.0.0"
}
上节课我们介绍过,“任务” 的概念,就是在 VSCODE 中做特定工作时,可以把经常要完成的操作,定义成一个任务。这概念不够严谨,现在我们为 “操作” 加上必要的限制:特指需要(以及可以)通过执行某个命令行程序可完成的操作。套用到本节课,编译一个 CPP 源文件,显然可以通过执行 g++.exe 这个程序来完成(如果你忘记这一点,请复习本课程第一节课,再写一次编译源文件到可执行文件的命令行以加深印象)。
☞ 字段解释:
- type : VSCODE 允许用户为任务指定 shell 、process、typescript、npm、gulp 等固定几个类型,以方便进一步贴合命令的具体执行细节;不过,扩展可以注册新类型。对于C/CPP编译任务,程序员可以自己写一个 shell 类型的指令,也可以使用我们已经安装的 C/C++扩展,当使用后者,推荐使用扩展注册的 “cppbuild” 类型(目的就是为了让 VSCODE 更清楚这个任务在干什么);
- label : 既是用来在VSCODE界面上显示的名称,也是该任务的惟一标志;
- command:该任务要调用的外部命令,本例是 g++.exe;
- args: 调用 g++.exe 时,需要的入参。如果你有好好上第一节课,那你至少应该认识其中的
-o
,并且,你应该懂得添加什么参数,可以实现静态链接,添加什么参数,可以实现对中文输出更好的支持…… - options: 指定调用 g++.exe 时,一些外在条件,比如此处指定 cwd(current work directory)指定在哪个目录下调用 g++.exe,我们指定的是 g++.exe 的所在目录;
- problemMatch:对 g++.exe 的执行输出内容,进行类似正则表达式的格式判断,以区分 g++.exe 是否执行(编译)成功,如果不成功,报错内容是什么;
- group: 分组
- detail: 详细说明,也用于在 VSCODE 的界面上展现;
☞ 编译参数解释:
- -fdiagnostics-color=always : 让 g++ 输出内容中总是加上颜色控制符(比如出错信息中的部分内容以红色显示),更多类似参数,见 “Diagnostic Message Formatting Options ”;
- -g :生成可执行文件中,包含有调试信息;
- -o : 我们用过了,它的下一个参数,必须是编译后要生成的文件;
- ${fileDirname}\${fileBasenameNoExtension}.exe :编译后要生成的文件。
因为 cwd 设置成 g++.exe 的所在目录,所以参数中编译后生成的文件名,必须指定完整路径,否则就会在类似 “c:\msys64\ucrt64\bin” 这样的目录下生成,这可太糟糕了……
当下,我们希望生成文件和源文件放在一起,可通过 VSCODE 支持的变量以获得一些文件信息,当前 tasks.json 中用到:${fileDirname} 和 ${fileBasenameNoExtension}。
设活动文件为: “c:\abc\HelloVSCCppSimple\helloVSCode.cpp”,有:
变量名 | 含义 | 值 |
---|---|---|
${fileDirname} | 文件所在路径 | c:\abc\HelloVSCCppSimple |
${fileBasenameNoExtension} | 无路径,无扩展名的文件名 | helloVSCode |
可证:本例编译所得文件将是 “c:\abc\HelloVSCCppSimple\helloVSCode.exe”。
正由于 只能编译当前活动文件(且该文件显然得是C/C++源文件),所以这个任务的标识才会叫做 “C/C++: g++.exe 生成活动文件”。我们将在下一节课加以改进。
5.2.2 详解 launch.json
打开 launch.json:
{
"configurations": [
{
"name": "C/C++: g++.exe 生成和调试活动文件",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
"args": [],
"stopAtEntry": false,
"cwd": "C:\\msys64\\ucrt64\\bin",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe",
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "将反汇编风格设置为 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: g++.exe 生成活动文件"
}
],
"version": "2.0.0"
}
“launch” 是“启动”之意。可以把它理解成一种特殊的任务,常用于调试。基于它可实现先编译得到可执行文件(比如上述的 helloVSCode.exe),再然后调用调试器(比如 gdb.exe)打开这个可执行文件,开始调试。
调试之前,得确保完成编译,所以请注意文件底部的 “preLaunchTask”,它指定在调试之前需要先调用的任务 “C/C++: g++.exe 生成活动文件” ,它正是前面 tasks.json 中任务的 label。 :( 没错,就是靠一个字符串来完成关联,这意味着,两边的字符串但凡有一点点不同(比如多个空格,字母大小写不一致等),关联就建立不起来——换句话说,就是:如果要改就必须两边一起改。
☞ 字段解释:
解释中的粗体字,表示当前设置。
- name : 启动任务的名字,用于在 VOCODE 界面上显示给人看;
- type : 我们安装的 C/C++ 扩展使用 cppdbg 表示使用 gdb 或 lldb 调试器,cppvsdbg 表示基于微软自家的C++调试器,二选一,用户别无选择;
- request : launch 表示由调试器打开待调试的程序,开始调试控制;attach 表示待调试的程序已经在运行,调试器将以“灵魂附体”的方式开始调试控制;
- program:待调试的程序,显然,它和 tasks.json 设置的待生成的可执行程序必须一致;
- args : 这回它是指要传给待调试程序的参数,即我们写的程序所需的命令行参数;
- stopAtEntry:调试时不管3721,直接先停在 main() 入口;通常不需要;
- cwd : 这回它是指待调试程序的工作目录,当前是编译器所在目录,不合理,以后会我们改成工程所在目录;
- environment 调试程序所需要的特定系统环境变量,记住这个用法,有时候它有大用;
- externalConsole:是否使用外部终端运行程序,在本课最后即有大用;
- MIMode:gcc/g++编译出来的程序,也可以使用 lldb 调试器(来自Apple),我们选择传统的 gdb;
- miDebuggerPath:调试器的完整文件名,结合上一项,它就是 gdb.exe 的完整名称;
- setupCommands:调试器自身的启动选项,记住它,后面处理中文编码问题时,需要用到;
- preLaunchTask: 就是 “pre” - “launch” - “Tash”,前面已解释。
5.2.3 修改工作区 C/C++ 设置
也许在某个项目,我们希望使用 C++ 20 标准写代码,而不是之前在用户级配置中填写的 17 标准,这时有一个比修改工作区级设置更方便、高效的方法来通知“智能小助手”这一变化。
在 VSCODE 中按下热键 :Ctrl + Shift + P,在弹出的命令面板中,输入 “C/C++ 编辑配置”,效果如图:
选择其中的 UI 方式, 将进入一个带有详细说明的图形界面:
再往下滚动,可以找到 “编译器路径”、“C标准”、“C++标准” 等更多用于 “IntelliSense” 的配置。以编译器路径为例,此时可以直接从下拉列表中选择:
以上配置将在当前工作区中的 .vscode 目录下生成 c_cpp_properties.json文件,内容如下:
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"cStandard": "c17",
"cppStandard": "c++20",
"compilerPath": "C:/msys64/ucrt64/bin/g++.exe"
}
],
"version": 4
}
这些配置同样只用于写 C/C++ 代码时的智能提示,不会修改项目真实使用的编译器、C/C++语言标准。
5.3 编译!调试!运行!
5.3.1 基本过程
其实,哪怕这俩 json 的内容一点都不懂,也是可以开始编译、调试、运行的,因为在没有遇到问题之前,无脑跟随式操作真的很简单……
①:确保活动文件是 helloVSCode.cpp (本文后续简称 .cpp 文件);② 点文件页右上角, “小虫子”图标靠右的下拉按钮;③ 选择 “Debug C/C++ File” (另一个菜单项是只运行不调试,但生成的可执行程序仍然带有调试信息)。
完成以上三步,还差最后一步:④ 选择调试配置:
没错,你现在看到的 “C/C++: g++.exe 生成和调试活动文件”,正是来自 launch.json 的 name 字段,点它,程序就会跑起来,并默认在 VSCODE 的内嵌终端输出,然后结束。
♨ 热键!热键!热键!
如果我现在说出:以上 ②、③、④ 其实都可以用热键 F5 代替,你会不会生气啊?会?好吧,那我不说了。
编译、执行成功后,在VSCODE 底部终端,会有如下内容:
如果看不到终端或不小心给关闭了,可以试试热键 Ctrl + ` (其中
`
是键盘左上角,Esc 键下方,和~
共用的那个按键)。另,如果你使用 Rime 输入法,可能发生热键冲突,建议修改该输入法的热键,或临时切换到别的输入法。
有关 C/C++ 程序在 VSCODE 中的更多调试操作与技巧,我们会有专门的课堂讲解,现在简单说说最常用的两项:设置断点和观察变量。首先修改一下代码:
#include <iostream>
using namespace std;
auto main() -> int
{
string hello = "Hello VSCode.";
cout << hello << endl;
}
然后在点击第 8 行左侧(或者鼠标点第8行代码,然后按 F9),可添加一个调试断点。最后按下热键 F5 ,程序将运行且停在该行:
①、②、③ 就不说了,④ 所指的侧栏,称为 vscode 的调试侧栏,⑤ 处会列出断点上下文存在的本地(Locals)变量,当前我们只有一个 std::string 变量,名为 hello,值为 “Hello VSCode.”,正是新代码用于输出的问候内容。
此时,界面还有一个在断点处冒出的家伙需要我们关注,它叫调试操作(浮动)栏:
☕ 课堂作业-1
想办法了解该调试栏上各个按钮的作用,并记住各自热键。建议先试试图中显示的 “单步调试(F11)”。
最后,再按一次 F5 ,或者通过调试栏上的第一个小按钮,全速运行从而结束本次调试。
5.3.2 只编译,不调试,不运行
现在,不管要不要调试,程序在编译之后都会跑一遍。有时候我们只想编译,怎么办?
确保 .cpp 文件处于活动状态,主菜单:终端 → 运行生成任务。如图:
♨ 又一个重要热键!
不是建议,而是教学要求:记住图中显示的热键: Ctrl + Shift + B(助记:B=Build)。
接着,VSCODE 弹出一个有点面熟的步骤:
⚠ 注意! 图中有两选项,请选择 “调试器生成的任务” (该文本来自 tasks.json中的 “detail” 字段)。另外一个写着 g++.exe 完整路径的选项,来自 “智能小助手” 的推荐。
这一步称为 “选择构建任务”,和5.2小节的第④步很接近,但其实不管是显示的内容还是实际操作都大有不同。5.2小节的 “④ 选择调试配置” 将先执行构建任务(编译操作),然后开始调试或运行;现在这一步则只有 “构建任务”一步。
☹ 编译一个文件,还分两步操作,太烦人了! 以下是解决方法。
请继续通过主菜单: 终端 → 配置任务生成任务;在下一步中同样选择 “调试器生成的任务”。 vscode 会把 tasks.json 打开,并选中匹配该任务的 json 数据( tasks 数组中 唯一 的那个任务,注意其 detail 字段的值)。关闭 tasks.json ,切换到 helloVSCode.cpp,现在再按下 Ctrl + Shift + B,将立即开始构建过程。
☕ 课堂作业-2
打开 tasks.json 或 launch.json,然后按下 Ctrl + Shift + B,看看结果,然后去 d2school.com 找到本课堂并进入作业区提交你看到的内容。
5.4 静态编译
如果你有学过本课程的第一课,你知道我在说什么。
Ctrl+` 打开终端输入 dir
,确保存在 helloVSCode.exe (后续行文简称 .exe ),输入 .\helloVSCode.exe
并回车,你将什么也看不到(就像第一节课那样)。
打开 tasks.json , 在字段 args 数组中插入参数 -static,如:
"args": [
"-static", // 插入的新参数,用于静态编译、链接,注意英文逗号
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe"
],
保存 tasks.json,切换到 .cpp 文件并再次构建(最后说一次热键: Ctrl + Shfit + B),再次运行 .exe 文件。两次运行对比如下。
6. 汉字、汉字、汉字
(以下问题,不仅仅是汉字,日文、韩文、希伯来文,都存在)。
先把代码中 hello 的值,修改成 “你好,VSCode。这里是第2学堂:www.d2school.com” 。
6.1 消除 UNICODE 符号警告
代码中出现汉字标点后,迎面而来的,是一个非常黄色的问题。
我以为这是 BUG,可它竟然是一个 Feature!如果你和我一样不喜欢,请按如下步骤关闭该特性:
- 进入设置页,确保 选择 “用户” 级别;
- 输入过滤 :
Editor UnicodeHighlight.ambiguousCharacters
; - 取消 “Editor > Unicode Highlight: Ambiguous Characters” 的打勾状态。
马上切换到 “helloVSCode.cpp”,哇,人间净土!
☕ 为什么会这个特性?
代码中用到的一些非 ASCII 的符号,不容易辨别,VSCODE 担心你写错或看错。比如本意是要一个
!
,结果输入的是!
。
6.2 确保汉字输入输出正确
6.2.1 问题观察
首先,确保让 helloVSCode.cpp 处于活动,然后看一眼底部状态栏右侧是不是有显示 “UTF-8”——如果第一节课你是认真学的,你可能猜到我们要做什么了。
没错,由于当前源代码采用UTF-8编码,并且未通过 “-fexec-charset=GBK” 参数在编译过程转成GBK 编码,所以,当程序直接在终端内运行,乱码将如约而至。
浣犲ソ锛孷SCode銆傝繖閲屾槸绗?瀛﹀爞锛歸ww.d2school.com
什!么!都!不!要!动!现在要做的是:确保 .cpp 文件活动(最后提醒一次),然后按下 F5,你将看到:
作为一名优秀的程序员,我们必须清楚所有会影响程序运行结果的底层逻辑。先看图中红圈中的内容,这是 vscode 在我们刚刚的一系列操作过程,打开的三个内嵌终端,分别是:
- powsershell(有也可能是 cmd ),我们使用 Ctrl + ` 打开的终端,在这个终端直接运行程序,乱码;
- C/C++: g++.exe … : 我们按下 Ctrl + Shift + B 后,用于调用 g++.exe 编译代码的终端,vscode 在启动该终端时,会先将它转为 UTF-8 编码,因此该终端正确。(但某个版本之前并没有做这件事,我个人记得是不少中国用户反馈了后做了修改,未验证);
- cppdbg: helloVSCode.exe 我们按下 F5 启动调试时,打开的终端,此时 VSCODE 没有将该终端设置为UTF-8编码;不过,它实际运行的是调试器程序 gdb.exe (这层关系,可图中蓝色文字末尾处发现端倪),gdb.exe 依据宿主终端的编码(GBK)作了些处理,然后打开 .exe 文件,没有乱码。
中文 Windows 中, PowserShell 或 cmd 默认使用的都是汉字国标(GBK 或 GB18030),运行我们所写所编译的,使用 UTF-8 编码的程序,乱码。
cppdbg 调用的 gdb.exe ,它没有历史包袱,一直就使用 UTF-8 编码,并且其 Windows 版本(也就是 msys2-ucrt64),会主动将自己的运行环境,修改成 UTF-8 编码,因此不会乱码。(但是注意:由于VSCODE的疏忽,启动 gdb.exe 的终端自身,仍然是 GBK 编码,这是 gdb.exe 作为子进程无法改变的,因此本课堂后面很快又会碰到新问题)。
☕ 课堂作业-3
验证以上说法。先进入图中的 powershell 输入
chcp.com
回车;再进入 cppdbg 所在终端同样输入chcp.com
,查看并对比二者输出内容。
6.2.2 再见 -fexec-charset=GBK
假设为 g++ 的编译参数 (tasks.json 中的 args 数组)加上 “-fexec-charset=GBK” (可放在刚加的 -static 下面),结果令人哭笑不得:在 Powershell 中运行 .exe ,没有乱码;在 cppdbg 所在终端中运行,乱码。
一点吐槽:坦率地说, 微软官方提供的这个 C/C++ 扩展,对 GCC 的支持不是很好(特别是在 Windows 下,在 Linux 倒无大碍),不仅仅是编码处理,而是在很多边边角角处有各类让人难受地地方……(不然的话,我也不会长年花钱买 CLion).
直接给结论:我们的C/C++程序,必须告别使用 GBK 编码。无论是在源文件直接使用 GBK 还是通过编译参数转为 GBK,都是不专业的做法,不符合标准,也难以跨平台,难以和涉及编码的三方库配合,也难以和其它进程交换编码敏感的数据……
当然,这事说起来不能都怪微软,当年是我们国家要求中文Windows必须使用 GB 开头的一系列标准……
如果我们不考虑在 vscode 集成的终端独立运行编译后的程序,是不是一切就OK了呢?也不行,请看的代码:
// 汉字输入编码问题的示例程序
#include <iostream>
using namespace std;
auto main() -> int
{
string name;
cout << "请输入你的中文姓名:";
getline(cin, name);
string hello = "你好," + name + "大师!";
cout << hello << endl;
cout << endl;
}
调试这段代码,输出内容中的汉字一切还,但输入给 name 的姓名,它!仍!然!是!GBK编码!以下是调试过程截图:
简单地说,来自源代码UTF-8编码的汉字,显示正常,来自程序运行后,由输入法(操作系统层面)输入的汉字,还是GBK……
VSCODE 要解决这个问题并不难,事实上在Windows中独立运行的终端之前也有这个问题,但后它就解决了——在它没解决之前,逼得好多人只好通过扩展 cpp 的 cin 实现以解决这个问题,我就有一个自认还不错的版本,只是为了标准输入输出,还要“随身携带”个第三方库,对初学者太不友好了……。
估计向VSCODE官方反馈这个问题的人多了以后,VSCODE会修改的(几个字符的事)。
6.2.3 实现全程 UTF-8 编码,并解决问题
我们不能等。让我们梳理一下当前状态,以下是好的一面:
- 当我们写源代码时,使用的是UTF-8编码,很好,很标准,很通用,很工业化;
- 当我们编译以生成可执行程序包含的字符串,也保持 UTF-8 编码,很好;
- 我们调试程序时,gdb.exe 对 UTF-8 编码也有原生支持;
- 我们的程序,可以通过调用外部指令,修改所在终端的编码,这对Windows的外部独立终端可行。
再来看不好的一面:
- 中文 Window 终端,无论 PowShell 或 cmd,都默认使用GB系列的编码;
- 如果我们的程序主动修改所在终端的编码,可以解决问题,但 VSCODE 又有一个问题:它所集成的终端如果被所运行的程序修改了编码,整个终端的输出内容将会 “乱跳”,包括光标位置都开始错乱……
- gdb.exe 也是个老实人,它在读到所在终端编码不是UTF-8时,调试时,自某个版本时,它会努力去尝试解决这类编码问题,可惜的皇帝不急太监急,效果有限,有时添乱。
解决方法:
- 放弃使用 vscode 内置终端来调试运行程序;
- 改为使用外部终端,并主动在程序一开始时就修改自身运行终端的编码为 UTF-8;
- 明确告诉 gdb.exe ,保持初心,继续用UTF-8编码,别乱操心;
看起来有点无奈,但其实让程序在外部终端运行(不影响调试),会有很多的好处,有时候还是必须的(因为毕竟外部独立终端能力最强),篇幅关系这里不展开了。下面来看具体操作步骤。
第一步:打开 launch.json,将其中的 externalConsole 字段由 flase 改为 true。
第二步:在 “setupCommands” 字段(也是一个数组),加入 gdb.exe 运行的一个初始化指令。
现在, launch.json 内容如下,修改部分见注释。
{
"configurations": [
{
"name": "C/C++: g++.exe 生成和调试活动文件",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
"args": [],
"stopAtEntry": false,
"cwd": "C:\\msys64\\ucrt64\\bin",
"environment": [],
"externalConsole": true, // <----- 改在外部终端运行
"MIMode": "gdb",
"miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe",
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "将反汇编风格设置为 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
},
{ // 新加的 gdb.exe 初始指令 ----->
"description": "指定GDB使用UTF-8",
"text": "-gdb-set charset UTF-8",
"ignoreFailures": false
}
],
"preLaunchTask": "C/C++: g++.exe 生成活动文件"
}
],
"version": "2.0.0"
}
然后,.cpp 内容在 main() 入口添加一行外部指令调用,同时对应增加对 <cstdlib> 头文件的包含,完整代码如下:
#include <cstdlib> // <-- 用于 system 函数
#include <iostream>
using namespace std;
auto main() -> int
{
system("chcp.com 65001 > nul");
string name;
cout << "请输入你的中文姓名:";
getline(cin, name);
string hello = "你好," + name + "大师!";
cout << hello << endl;
cout << endl;
}
必要解释如下:
- chcp.com 是用于修改所在终端编码的 Windows 命令( com 是 command之意);
- 参数
65001
是 Windows 对 UTF-8 编码的代码; - 参数
> nul
作用是让 cmd 在执行该指令但不显示结果,如果终端是 PowerShell,应改为> null
;
task.json 应保持不变,如果你添加过 “-fexec-charset=GBK”,请去除干净。
F5 编译、运行、并接受调试……
以上就是编码问题的观察、理解与解决的全过程。当然,如果你写的程序无关编码,或者无关输入,那么可继续使用VSCODE的内嵌终端,但 launch.json 中添加的 gdb.exe 初始指令,还是要加上。