一、前言

​ 这篇文章是windows系统下的VSCode的C++项目(C应该也差不多)的多文件编译的教程(未使用CMake),笔者也才刚刚学会方法,如果有什么错误或者问题还请指出。这个教程是循序渐进的,所以有基础的或者只是想知道launch.json和tasks.json怎么配置的可以直接看最后面的配置代码,但是如果是想知道为什么要这么配置的建议全文看完并跟着做一遍。

二、准备工作

1、下载VSCode(废话),官网链接

2、下载MinGW编译器,官网链接

​ 这个网站国内可能进不去,但是网上有很多资源和安装教程,或者去b站搜索一下教程,然后一定要把环境变量配好,这个并不是很难,所以就不细说了。

2、安装插件

​ 打开VSCode,点击左侧的扩展选项:

​ 安装简体中文插件(英语好的可以不安装):搜索chinese即可

​ 安装C/C++插件:搜索C/C++即可,这个插件建议不要下载最新版本,如果已经安装了最新版本,可以点击卸载按钮,在下拉框中选择安装之前的版本,我是1.8.4版本。新的版本在后面生成launch.json时可能不会给出预制模板。

三、正式开始

1、创建工程

在电脑中合适的位置创建一个文件夹(假设文件夹名为Test)作为项目文件夹,然后在VSCode中点击左上角文件->打开文件夹,打开刚才创建的文件夹,创建新的文件,创建方式有以下两种(其实不止两种,只是这两种比较方便):

  1. 在左侧项目文件夹处点击
  2. 在左侧项目文件夹空白(虽然是黑的)处双击:
  3. 方法2的空白处右键后选择新建文件夹。

​ 创建一个名为main.cpp的cpp文件,写一个最简单的helloworld程序:

​ main.cpp:

1
2
3
4
5
6
7
8
9
#include <iostream>

using namespace std;

int main()
{
cout << "Hello world!" << endl;
return 0;
}

2、控制台编译(非常重要,建议一定要跟着做一遍才能理解后面在干什么)

1、单文件编译

​ 点击终端->新建终端,然后下面就会出现一个终端窗口,他会直接进入到工程目录下:

​ 在终端中键入g++ .\main.cpp回车,g++意思是使用g++编译器,如果是C项目则使用gcc编译器,后面的.\main.cpp是要编译的文件(.\是当前文件夹的意思),编译完后Test目录下就会出现一个名为a.exe的可执行文件,这时候在终端中键入.\a.exe回车,就会执行这个文件,若终端中显示Hello World!就说明编译没有问题。

​ 然后在终端中键入g++ -g .\main.cpp -o main.exe回车,这时候会发现Test目录下会出现一个main.exe,此时在终端中键入.\main.exe 回车,那么终端中依旧会显示Hello World!,那么a.exe和main.exe有什么区别呢?

​ 我们可以在终端中键入ls回车查看这两个文件的大小:

​ 我们发现,main.exe比a.exe大了一些,那么我们来解释一下g++ .\main.cppg++ -g.\main.cpp -o main.exe

​ g++是使用g++编译器编译(C++文件),-g是指编译出的可执行文件中包含调试信息(所以main.exe会比a.exe大)这样这个文件就可以被调试了(调试的事情之后再说),-o是指定生成的可执行文件的名称。

2、多文件编译

​ 在项目文件夹中再创建hello.cpp和hello.h两个文件,并在hello.h中声明一个名为printHello的函数:

​ hello.h:

1
void printHello();

并在hello.cpp中实现这个函数,在main.cpp中调用这个函数:

​ hello.cpp:

1
2
3
4
5
6
7
8
9
#include "hello.h"
#include <iostream>

using namespace std;

void printHello()
{
cout << "Hello World!" << endl;
}

​ main.cpp:

1
2
3
4
5
6
7
#include "hello.h"

int main()
{
printHello();
return 0;
}

​ 我们先将之前生成的a.exe和main.exe删除了(项目中选中文件右键删除即可,若显示无法删除则说明你的程序正在运行),这时我们在终端中键入g++ -g .\main.cpp -o main.exe回车,此时我们发现项目中并没有生成main.exe,并且终端报错:

​ 这是因为main.cpp调用的方法是hello.cpp的,但是我们只编译了main.cpp,故而报错printHello方法未定义。

​ 另外,如果在上面的main.cpp中若#include "hello.h"下方显示有波浪线(一般情况下不会有这个错误,因为头文件是在项目文件夹中的),并报错找不到头文件,则可以按下ctrl + shift + p在弹出的搜索框中键入C/C++ Edit Configurations ,选择C/C++ 编辑配置(JSON),如果没有安装中文插件的选择C/C++ edit configration(JSON)

​ 这时项目中会出现一个名为.vscode的文件夹,并且里面有一个名为c_cpp_properties.json的文件,打开后如下显示:

​ 其他的不同没什么关系,也不一定要和我配的一样,主要是"includePath"字段,"${workspaceFolder}/**"表示包含你这个项目文件夹中的所有文件(/**表示递归包含),如果你的头文件在别的文件夹中(不在项目文件夹中,也不在项目文件夹的子文件夹中),就需要把那个文件夹的地址写进这里,或者你可以直接把那个文件夹拷贝到项目文件夹中。但其实这个只是影响编写代码的智能补全,并不影响之后的编译。

​ 既然如此,那是不是只要把hello.cpp和main.cpp都编译了就行了呢?我们在终端中键入g++ -g .\main.cpp .\hello.cpp -o main.exe回车,这时项目文件夹中出现了main.exe,且终端也没有报错,说明编译成功,键入.\main.exe终端中也显示出了Hello World!。

​ 那么也就是说多文件其实也可以编译成功,但是如果我们每一次编译都要这样输入的话太麻烦了,而且这种编译的方法不就是传统的在控制台中编译的方法吗?这甚至和vscode没有什么关系(只是我们借用了vscode的内部终端而已),我们直接按win + r键入cmd,直接在cmd中也可以这样编译,那么vscode有没有什么好的方法可以让我们更快的编译?这时就需要用到launch.json和tasks.json了。

3、vscode编译

1、配置launch.json和tasks.json

点击左侧调试按钮:然后点击创建launch.json文件。

​ 选择C++(GDB/LLDB)

​ 再选择g++.exe - 生成和调试活动文件

​ 如果在选择了C++(GDB/LLDB)后就没有这一步选择的话那么可能的因为C/C++插件的版本问题,因为笔者之前安装最新版本时出现过这个问题,但如果你安装的是旧的版本还有这个问题,那么我就不知道是怎么原因了(因为我没有遇到过,当然不知道怎么处理)但是也不要慌,这个问题只是会让你的launch.json文件没有预制的模板,你可以去网上找一个模板,或者按照我下面给出的代码配置。

​ 此时会进入编译过程,然后显示调试失败:

​ 直接×掉这个提示框(如果点击了打开"launch.json"则会出现之前的选择配置的选择框,按esc关掉选择框就行了)。此时项目中会有.vscode文件夹(可能你之前就有了,反正无论如何现在有这个文件夹就行)。里面会有launch.json和tasks.json两个文件(之前配置过includePath的话还会有c_cpp_properties.json),其中launch.json是调试的配置,tasks.json是编译的配置。我们先打开launch.json,正常情况下会是下面这个情况(当然不会有我这么详细的注释):

​ launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [//没有加注释的地方可以自己把鼠标放在相应的kay上会有说明(注意是在vscode里的launch.json里把鼠标放在相应key上,你在这个makedown文档上当然没有用)
{
"name": "g++.exe - 生成和调试活动文件",//这里就是你刚才点创建launch.json选的配置
"type": "cppdbg",//cpp文件debug
"request": "launch",
"program": "${fileDirname}\\${fileBasenameNoExtension}.exe",//需要调试的文件的路径,${fileDirname}就是你之后按下调试按钮时正打开的文件所在的文件夹,${fileBasenameNoExtension}就是你按下调试时正打开的文件的文件名,与tasks.json的args下的-o指定的位置对应
"args": [],//调试时传递给程序的参数,一般为空
"stopAtEntry": false,//在目标的入口点暂停(main函数处暂停),相当于在入口处加了一个断点
"cwd": "${fileDirname}",//调试程序所在的目录,${fileDirname}上面已经解释过了
"environment": [],//环境变量
"externalConsole": false,//是否使用单独的窗口,如果是flase,就会在vscode的内置终端中调试,true则会打开一个cmd窗口进行调试
"MIMode": "gdb",//调试器,可以是gdb或者lldb,注意,你的MinGW里一定要有这个调试器,像我之前不知道搁哪下载的MinGW居然没有,一般是有的
"miDebuggerPath": "D:\\MinGW\\MinGW\\bin\\gdb.exe",//调试器的地址
"setupCommands": [//好像可以更好的显示STL容器内容,没怎么研究(反正我之前用的STL的stack或者vector之类的数据结构感觉调试时都不是很好显示内容)
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "将反汇编风格设置为 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: g++.exe 生成活动文件"//调试前的任务,一般是编译程序,所以他这里是和tasks.json的label对应的,如果是没有安装中文插件的话,他是英文的,但是不要紧,只要和tasks.json的label对应就行
}
]
}

​ 如果你的launch.json只有两行:

1
2
3
4
{   
"version": "0.2.0",
"configurations": []
}

​ 那么就是我上面说的没有预制模板,可能最新版本是C/C++插件的问题,但是你可以直接把我的这个配置复制过去,然后把"miDebuggerPath"的value设置成你安装的MinGW里的gdb的地址(或者你选用别的调试器,那么"MIMode"的value也改成你选的调试器),并且你的"preLaunchTask"的value要和你的tasks.json里的"label"的value对应。

​ 然后我们再打开tasks.json:

​ tasks.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"tasks": [//没有加注释的地方可以自己把鼠标放在相应的kay上会有说明(注意是在vscode里的tasks.json里把鼠标放在相应key上,你在这个makedown文档上当然没有用)
{
"type": "cppbuild",//编译类型,cppbuild就是cpp文件编译,还可以是shell,具体上面区别可以百度
"label": "C/C++: g++.exe 生成活动文件",//任务名称,要和launch.json的"preLaunchTask"字段对应
"command": "D:\\MinGW\\MinGW\\bin\\g++.exe",//要使用的编译器的路径
"args": [//这个东西就是我们之前控制台编译时g++ 后面的参数,除了以下的参数外还有很多参数,具体情况可以百度,这些就够我们多文件编译了
"-fdiagnostics-color=always",//编译时警告信息是彩色的
"-g",//熟不熟悉?就是之前控制台编译时输入的-g,表示生成调试有关信息,这样编译生成的可执行文件就可以调试了
"${file}",//要编译的文件,我们发现这样的话就只能编译单个文件了,怎么修改看之后的操作
"-o",//指定生成的可执行文件的名字,不加的话就是a.exe
"${fileDirname}\\${fileBasenameNoExtension}.exe"//${fileDirname}\\${fileBasenameNoExtension}表示的地址在launch.json说过了,一定要和launch.json的"program"对应
],
"options": {
"cwd": "${fileDirname}"//当前工作目录
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true// 开启编译快捷键,ctrl + shift + B
},
"detail": "调试器生成的任务。"
}
],
"version": "2.0.0"
}

​ 在这两个文件中最重要的就是

  • ​ launch.json的"program"要和tasks.json的"args"的"-o"所指定生成的可执行文件的名字要一致,不然找不到要调试的文件。
  • ​ launch.json的"preLaunchTask"要和tasks.json的"label"对应,不然调试前无法编译。

​ 我们先将之前控制台编译生成的main.exe删除,然后根据之前控制台编译的情况,我们将tasks.json中的"args"作如下修改:

1
2
3
4
5
6
7
8
"args": [
"-fdiagnostics-color=always",
"-g",
"${workspaceFolder}\\main.cpp",//${workspaceFolder}表示当前的项目文件夹路径
"${workspaceFolder}\\hello.cpp",
"-o",
"${workspaceFolder}\\main.exe"//设置生成的可执行文件的路径
],

​ 然后将launch.json的"program"设置成 "${workspaceFolder}\\main.exe",(与tasks.json的"-o"参数对应):

1
"program": "${workspaceFolder}\\main.exe",

​ 我们点击上面的运行->启动调试:(或者按快捷键F5)。

​ 这时下方的终端中的C/C++:g++.exe生成活动文件窗口会显示生成成功完成:

​ 然后点击调试控制台,里面会显示Hello World!说明编译正确,并且程序成功运行。

​ 如果你的launch.json中的"externalConsole"设置成true,则表示调试时在控制台中运行程序,那么你点击调试后会有一个窗口一闪而过,因为main函数执行后就退出了,可以在main函数的return 0;之前加一句getchar();(注意导入头文件)来避免这个情况。点击调试后是下面这个情况。

​ 这种方式来编译其实就相当于在控制台编译中键入g++ -g .\main.cpp .\hello.cpp -o main.exe,那么如果有很多文件岂不是很麻烦?要把每一个要编译的cpp文件都写进tasks.json?其实可以将tasks.json的"args"进行如下设置:

1
2
3
4
5
6
7
"args": [
"-fdiagnostics-color=always",
"-g",
"${workspaceFolder}\\*.cpp",
"-o",
"${workspaceFolder}\\main.exe"
],

"${workspaceFolder}\\*.cpp",就表示将工作目录(也就是项目的目录)下的所有cpp文件都编译,此时再启动调试,依旧成功编译,成功调试。

​ 那么如果我的头文件和cpp文件都是在不同目录下的呢?例如,在项目目录下创建两个文件夹(和创建文件的方式差不多,在Test下的空白区右键创建),分别叫heads和sources,然后将hello.h放到heads文件夹中,将main.cpp和hello.cpp放到sources文件夹中。

​ 此时启动调试会发现终端中的C/C++:g++.exe生成活动文件窗口报告编译错误:

​ 但是调试控制台(或者cmd窗口)依旧显示了Hello World!。

​ 编译错误是因为你的cpp文件已经不是直接放在工作目录(项目目录)下了(虽然sources目录依旧在工作目录下),那么就没有编译到cpp文件,从而报错。

​ 而调试控制台(或cmd窗口)中仍然显示Hello World!是因为我们只是规定调试前要做编译的工作,但是至于编译是否成功并不关心,甚至没有编译都行(例如将launch.json的"preLaunchTask": "C/C++: g++.exe 生成活动文件"注释掉),只要launch.json的"program": "${workspaceFolder}\\main.exe",的可执行文件是存在的并且可执行文件中有调试信息即可。编译失败只是没有重新生成新的main.exe,但是原来的main.exe依旧存在并且可以调试。

​ 那么我们可以将tasks.json的"args"进行如下设置:

1
2
3
4
5
6
7
8
9
"args": [
"-fdiagnostics-color=always",
"-g",
"${workspaceFolder}\\sources\\*.cpp",
"-I",
"${workspaceFolder}\\heads",
"-o",
"${workspaceFolder}\\main.exe"
],

​ 再启动调试,那么终端中的C/C++:g++.exe生成活动文件窗口就会显示生成成功:

​ 而此时的main.exe是新生成的main.exe(可以删除了main.exe之后再启动调试试试看)。

​ 对于"-g"后面的"${workspaceFolder}\\sources\\*.cpp"应该不需要再解释了吧,而"-I"是之前控制台编译所没有讲到的,其表示的是需要包含的头文件的文件夹,也就是"${workspaceFolder}\\heads"。我们可以在终端处试试控制台调试,新建终端,在终端中键入g++ -g .\sources\main.cpp .\sources\hello.cpp -I .\heads -o main.exe回车,没有报错就是编译成功(可以把main.exe删了再试试有没有重新生成main.exe),此时在终端中键入.\main.exe回车则显示Hello World!。

​ 如果你的项目中有别的地方的头文件那么也需要把他加入到"-I"后面

​ 由此,多文件编译就差不多配置好了,有一些建议:

  • 如果在上述操作中发现main.exe无法删除,那么是因为main.exe正在运行,得结束运行才行。

  • 我们可以把"-o"指定的生成路径改成"${workspaceFolder}\\bin\\main.exe",同样的launch.json的"program"后的路径也要改成"${workspaceFolder}\\bin\\main.exe"。并且在项目目录下建立bin文件夹(不然会报错),这样生成的可执行文件就在bin目录下了。

  • launch.json中的"cwd"和tasks.json的"options"下的"cwd"(可能你的tasks.json里没有这个东西,可以加上)设置成"${workspaceFolder}"

​ 最后贴上我最终设置的launch.json和tasks.json:

​ launch.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "g++.exe - 生成和调试活动文件",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}\\bin\\main.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "D:\\MinGW\\MinGW\\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 生成活动文件"
}
]
}

​ tasks.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: g++.exe 生成活动文件",
"command": "D:\\MinGW\\MinGW\\bin\\g++.exe",
"args": [
"-fdiagnostics-color=always",
"-g",
"${workspaceFolder}\\sources\\*.cpp",
"-I",
"${workspaceFolder}\\heads",
"-o",
"${workspaceFolder}\\bin\\main.exe"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "调试器生成的任务。"
}
],
"version": "2.0.0"
}

​ 做别的项目时只要相应的进行一些小调整就行。