加载中...
C/C++简捷项目专用配置
第1节:MSYS2+GCC 安装与应用
第2节:安装配置 VSCODE,理解「配置」和「设置」的区别
第3节:C/C++简捷项目专用配置
第4节:让C/C++简捷项目支持多文件编译
第5节:备战大项目:CMake专项配置
第6节:集成 AI 助手——MarsCode
课文封面
  1. C/C++ 扩展的安装、设置与小问题解决
  2. 使用 C/C++ 扩展,实现单一源文件编译与调试
  3. 汉字编码问题全新解决:输出、输出、GDB调试时的,全过程 UTF-8 统一编码方案

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 所在路径,可在路径前面加上 – 以取消它,如下图:

取消g++可执行程序全局路径环境变量

确定后,再用同样的方法检查系统变量。

如果你对以上操作不太熟悉,可以先不处理。本课稍后会有此项操作过程的录屏视频演示,到时看明白了再来操作也不迟。

0.4 FAQ

Q1:都是用 C/C++编程,为什么需要两套配置?

一套 《白话 C++ 之 练功》 ,或者一本《C++ Primer》 学下来,你可能需要写上百个 C/C++ 代码,并且代码基本是“日抛型”,学明白了,就马上要再创建下一个,在这类情况下,我们会更希望专注于学习本身,如果创建一个项目就要费上大半天,并且还会在磁盘上生成一堆文件),显然影响效率。

工作中,或者学习到一定水平后,我们要面对复杂的项目构建过程,典型如:

  1. 需要和不少第三方库打交道;
  2. 项目的构建过程有很多的选项,比如一个项目需要生成多个二进制文件等。
    这时候,使用简单方案无法胜任,我们需要引入成熟的构建管理工具。

我们为以上两种目的,分别创建一套VSCODE的专用配置,并且第一套配置可平滑升级到第二套。

Q2:为什么不合成一个配置?

技术上当然可以——事实上这种方法网上到处是,包括 VSCODE 官方文档。但需要用到的两个主要扩展配合得不好,一套项目有两种编译机制,并且在界面上将并存两套操作入口,加载慢、不清爽;有些入口已经失效但还留在界面上,有些入口一点去,就是一个误操作……

Q3:多套配置会造成相同扩展被安装多次吗?

不会,比如,我们在 A 配置下安装 K 扩展,然后大 B 配置下又安装 K 扩展,VSCODE 会很聪明地实质只在你的磁盘上安装一个 K 扩展,但可以实现同一个扩展在A配置、B配置下,各有各的设置,我们正是利用这个机制,在第二配置中,去掉不必要的UI入口。

1. 创建 C/C++ 简易配置

打开 VSCODE,如果它自打开之前的文件夹,通过主菜单 “文件 → 关闭文件夹” 关掉。然后通过主菜单 “文件 → 首选项 → 配置”,或者通过左侧工具栏,进入配置页面,如下图:

进入VSCODE配置页

为教学效果更清晰,我已删除之前创建的配置,只留下 VSCODE 自带的默认配置。

建议将默认配置用于日常编辑 TXT、JSON、MARKDOWN 等文本,只安装此类工作相关的,通常比较轻理的扩展;上图表明,我现在的默认配置,只安装了简体中文包扩展。

点击 “新建配置文件”,然后:

  1. 填写名称为 “CS-CPP-Simple”;
  2. 选择你喜欢的图标,建议就默认的小齿轮;
  3. 确保所有 “内容源” 都是 “无”;
  4. 点击页面右下的 “创建” 按钮。

2. 切换配置并安装扩展包

2.1 切换到 CS-CPP-Simple 配置

首先,在配置页的配置列表中,将 “活动” 配置,切换为刚刚创建的 “CS-CPP-Simple” (方法就是点击名称后面的打勾)—— 这一步很重要哦!

接着,使用 Ctrl + K, T,为你日常写简单的 C/C++ 程序,选一套主题。上节课我用的 蓝色主题,被学医的同学嘲笑了……算了,我就使用VSCODE安装自带 “现代深色”吧……

切换到新配置: CS-CPP-Simple

我们后面的多数操作,就在该配置下进行,并且多数明面上的操作结果只在该配置下发挥作用。

2.2 安装“纯” C/C++ 扩展

Ctrl + Shift + X,打开扩展商店面板,在过滤栏输入 C/C++ (纯英文字母或字符),稍等 片刻:

安装纯纯的 C/C++ 扩展

注意区别图中三个图标一样的扩展:

  1. C/C++:用于编写 C/C++ 代码时的智能辅助,以及配置外部编译器、调试器,实 C/C++ 代码的编译、调试、运行。这是我们现在需要的
  2. C/C++ Themes:不用安装。这是官方早先特意为 C/C++ 实现的可对代码进入带语义的颜色渲染,后来普通主题也支持了……
  3. C/C++ Extension Pack。 这是一个大一统包,包含了前两者以及CMake等更多扩展,现在严禁安装!

为避免后续在 “CS-CPP-Simple”配置下写 C/C++代码时,VSCODE 疯狂地推荐它,我们需要关闭 “C/C++ Extension Pack”的推荐选项。

① 在左侧列表中选中它,② 右侧扩展内容页点 “自动更新” 后面的小齿轮按钮,选择 “忽略建议”。

忽略C/C++大一统包的推荐

结论就是:日常写简单的 C/C++ 代码,我们只需要图中第一项全称为 “C/C++ for Visual Studio Code” 的扩展。请现在该项的 “安装” 按钮以完成安装。

切换到“配置文件”页,确保正确的扩展被安装到正确的配置下。

C/C++扩展安装在CS-CPP-Simple Profiles下

3 初识 VSCODE 设置

首先,请记住这个快捷键:“Ctrl + ,” (即:Ctrl 键加英文逗号),通过它,或者主菜单:文件 → 首选项 → 设置(Settings),进入(超级复杂的)设置页面。

在每个配置(Profiles)下,VSCODE的设置(Settings)又区分为用户(User)和工作区(也称工作空间)两级。同一用户打开的所有工作区,都默认启用用户级别的设置,除非用户在当前工作区中,采用了新的配置(称为:工作区设置项覆盖用户级设置项)。

☛ 重点理解:默认级 Vs. 用户级 Vs. 工作区级

  • 用户级别的设置作用范围大。一项设置,在用户级别设置合理,则多数工作区无需再配置;如果设置不合理,则几乎每新建一个工作区,你都需要刻意“调正”该项设置;显然,用户级别的设置,要谨慎
  • 工作区级别的设置优先级大。同一个设置项,工作区级的设置会覆盖用户级的值;通常,如果小项目也要在工作区做一堆设置,难忍;大项目因为用得久,所以可以忍
  • 出厂设置往往是兜底的值。事实上,还有一个更底层的级别:出厂设置。绝大多数设置项,都有来自VSCODE或扩展自身的出厂值;当既示做工作区和用户都未对某项设置修改时,使用的就是出厂默认值(${default}),大多数情况下,出厂默认值就是合适的那一个

注:如某个设置项通用所有配置,它会在设置标题后面加上 “(适用所有配置文件)”。

由于此刻我们的VSCODE没有打开任何文件夹,所以,在设置里,只会看到用户级别,如图:

初识VSCODE的设置(Settings) 页

点击左侧目录树中的“扩展”……并非只有 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++” 节点将看到:

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 ,如图:

设置C/C++语言标准为 2017

如果因为VSCODE升级或其他原因,造成你的过滤结果和上图略不同,可以考虑输入更精确的关键词:“C_Cpp Default Standard”。

4.2.2 设置编译器标准

同样,接下来的操作,只是要告诉“助手”,我们的C/C++程序默认使用的哪个编译器,而非限定我们以后写的程序,都只能使用这个编译器(当然,就学习而言,我们基本就是使用这个编译器)。

这回,在设置页面的过滤栏输入“Cpp Compile Path”(或 c_cpp.default.CompilerPat),结果如图:

准备修改C++编译器路径

明明就是一行输入的事,不懂 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 文件

新建 CPP 源文件

在 helloVSCode.cpp 中,输入以下代码,并保存。

#include <iostream> using namespace std; auto main() -> int { cout << "Hello VSCode." << endl; }

检查文件页的右下角是不是内容如下:

CPP 状态

  • 空格 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点钟方向,检查文件页的右上角是不是多出如下入口:

CPP文件的快捷操作入口

点击其中的 “小齿轮”,VSCODE将显示在本机上检测到的C/C++编译器:

VSCODE 检测到本机编译器

这里会有两种变化,一种是检测到多个编译器,说明你的电脑上安装多个C/C++编译器,评估仔细选择课程所用的 MSys3-UCRT64(或你之前的选择安装的环境,比如 MINGW64)。另一种可能是一个编译器也没检测到,这时尝试如下操作:

  1. 在 VSCODE 中按下 Ctrl + Shift + P,然后输入 C++Select IntelliSense Configuration;
  2. 选择 “在我的计算机上选择另一个编译器…”
  3. 找到之前的安装的 msys2-ucrt64 的 g++.exe……
  4. 再点击前述的 “小齿轮” ……

接上图,选择 “C/C++: g++.exe 生成和调试活动文件……” 选项,VSCODE 将在工作区的生成 .vscode 子目录,并于其下至少生成两个文件:tasks.json 和 launch.json,并且会自动打开后者,如图:

生成C/C++调试配置

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++ 编辑配置”,效果如图:

编辑C/C++配置,UI方式

选择其中的 UI 方式, 将进入一个带有详细说明的图形界面:

C/C++工作区配置名称

再往下滚动,可以找到 “编译器路径”、“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 符号警告

代码中出现汉字标点后,迎面而来的,是一个非常黄色的问题。

代码中的 UNICODE 符号会被加黄框

我以为这是 BUG,可它竟然是一个 Feature!如果你和我一样不喜欢,请按如下步骤关闭该特性:

  1. 进入设置页,确保 选择 “用户” 级别
  2. 输入过滤 :Editor UnicodeHighlight.ambiguousCharacters
  3. 取消 “Editor > Unicode Highlight: Ambiguous Characters” 的打勾状态。

禁用UNICODE字符警告功能

马上切换到 “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时,调试时,自某个版本时,它会努力去尝试解决这类编码问题,可惜的皇帝不急太监急,效果有限,有时添乱。

解决方法:

  1. 放弃使用 vscode 内置终端来调试运行程序;
  2. 改为使用外部终端,并主动在程序一开始时就修改自身运行终端的编码为 UTF-8;
  3. 明确告诉 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 初始指令,还是要加上。