文章框架
脑图前言
LOX(Lightweight Open-source Xplatform)是我正在编写的一个全新文集,意思就如其名:轻量级-开源-跨平台。就是这个系列中的文章和例程都符合轻量级(Lightweight)、开源(Open-Source)、跨平台(Cross-Platform)。嗯,我这人吧,就好起一些新的名词。
本篇作为LOX系列的第一篇,也向你展示了一个最简单的LOX项目。如果想看一个稍微复杂一点的LOX项目,可以clone我Github上的一个Repo:2048 CLI。欢迎来搞~
单源文件
先从简单的来,一个源文件。
工具
进行本次LOX开发所需要工具,点击下面的Title进入官网下载。
VSCode是微软出品的一款非常良心的轻量级编辑器,虽说是完全照着Sublime Text弄出来的,但它免费啊,还开源啊,所以我也就不深究模仿不模仿了。(嗨呀,原则呢?嗯?)
需要的插件
插件 | 标识符 | 说明 |
---|---|---|
C/C++ | ms-vscode.cpptools | C/C++语言支持,简单的编辑、编译、调试等功能。 |
这个工具是在Windows上需要的。因为Windows没有GNU工具包,MinGW就是为Windows而生的GNU工具包。
需要安装的包
包 | 说明 |
---|---|
mingw32-gcc.bin | GNU C编译器 |
mingw32-gcc-g++.bin | GNU C++编译器 |
mingw32-gdb.bin | GNU 调试器 |
环境变量
在系统变量的PATH
中,添加MinGW安装的根目录中的bin文件夹(如C:\MinGW\bin
),且尽量将这一项往上移(为了优先搜索该目录)。
完成这一步后,如果打开了VSCode,记得要重启VSCode。
编码
建立工作空间
新建一个文件夹 - 右键 - Open with Code。使用这种方法的前提是你在安装VSCode时选中了“”
或者在VSCode中 - 文件 - 打开文件夹,选中一个空文件夹作工作空间。
新建源文件
鼠标移动到新建的文件夹的根目录处(我的是“HelloWorld”),点击下图蓝色框住的图标(新建文件)。
之后在其中输入文件名(包括后缀名),本例的后缀名写".c"或".cpp"。然后在其中写个最简单的HelloWorld。
代码
#include <stdio.h>
int main(){
printf("Hello World");
return 0;
}
编码时,如果想自动补全,需要在包含指定头文件后保存代码,然后才会启动自动补全。
也就是说,它只对保存后的代码进行探测。
生成
生成快捷键:Ctrl(Mac: Command)+Shift+B
第一次生成时,VSCode会提示找不到生成的配置文件task.json
,点击“配置生成任务”。
在给出的模板中选择
Others
。在打开的task.json
中改为下述代码。
{
"version": "0.1.0",
"command": "gcc",
"isShellCommand": true, // 是否为Shell命令
"args": ["-g","${file}","-o","${fileDirname}/${fileBasenameNoExtension}.out"],
"showOutput": "always"
}
其实作用就是代码中所述的。
"command": "gcc"
和"args": ["-g","${file}","-o","${fileDirname}/${fileBasenameNoExtension}.out"]
就相当于在Shell中直接键入gcc -c 源文件.c -o 源文件所在目录/源文件.out
。将鼠标放在Key(冒号前面的)上会显示所对应的意思(下面的Launch.json同)。
简单的解释一下其中几个$
标识
标识 | 作用 |
---|---|
${file} |
当前文件的完整文件名(包括路径、文件名、后缀名) |
${fileDirname} |
当前文件的路径 |
${fileBasenameNoExtension} |
当前文件的文件名(不包括路径、后缀名) |
其中所说的“当前文件”指的是你在VSCode编辑器中打开的并正在编辑的文件。
当前正在编辑的文件
完成task.json
的配置后,我们缩写的程序已经可以运行了。生成的程序就是源文件所在根目录中出现的与源文件同名但后缀名为.out
的文件。
调试
我们当然不能仅仅满足于生成出程序就行的,大部分情况我们是要调试的。
调试快捷键:F5
第一次按F5
时和生成时一样,找不到配置文件,在打开的模板中选C++ (GDB/LLDB)
(可以跨平台)。
生成的配置文件为launch.json
将launch.json
改为下述代码
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch", // 本配置的名称,随便起
"type": "cppdbg",
"request": "launch", // 如果调试的类型为附加进程,需将这里改为attach
"program": "${fileDirname}/${fileBasenameNoExtension}.out", // 要调试的程序路径
"stopAtEntry": false, // 是否在起点处停顿
"cwd": "${workspaceRoot}",
"externalConsole": true, // 在外部控制台运行。若为false,则运行在VSCode自带的控制台中
"linux": { // Linux 系统下的配置
"MIMode": "gdb"
},
"osx": { // OS X系统下的配置
"MIMode": "lldb"
},
"windows": { // Windows 系统下的配置
"MIMode": "gdb",
"miDebuggerPath": "gdb.exe"
}
}
]
}
Windows默认是没有gdb或lldb的,所以我们需要安装MinGW中的gdb,并在这里设置gdb的路径。只写程序名是因为设置了环境变量。
完成launch.json
的配置并切换到源代码编辑页之后
再一次:F5
打上断点
合影留念
多源文件
什么!你不满足于单源文件开发?!来来来,我给你看个宝贝。
的确,单源文件开发一般也就是个做做算法题,现在随便写个像样的工程都是多个源文件编译链接一条龙的。这样VSCode可以吗,也可以!但需要新的帮手:CMake。
工具
新需要的插件
实际上通过CMake在VSCode上进行项目开发可以不需要任何插件,但是用上这些插件之后你会发现这个过程会变得特别方便!
除去单源文件中所提到的cpptools
,还需要下面的插件。
插件 | 标识符 | 说明 |
---|---|---|
CMake | twxs.cmake | 提供CMake语法支持,包括高亮和自动补全等 |
CMake Tools | vector-of-bool.cmake-tools | 这个屌!完全把CMake封装成一套VSCode底边栏的工具集 |
同样这一步仅在Windows上做。
需要新安装的包
包 | 说明 |
---|---|
mingw32-make.bin | GNU Make,根据makefiles(在这里makefiles由CMake搞定)生成项目 |
为了方便调用,我一般会把mingw32-make.exe
在其目录中复制一份出来,命名为make.exe
。
这一步某种程度意义上讲还蛮重要的,第一是方便自己调用(直接在shell里make
),第二是方便vscode的插件调用(如果不弄一个cmake.exe
出来的话,vector-of-bool.cmake-tools
会报错,不过具体锅是vector-of-bool.cmake-tools
还是twxs.cmake
的也不太清楚,目前vector-of-bool已经把这个问题(#157
)标记为bug
,并打算在0.10.0
版本解决。)。
安装时记得将Add CMake to system PATH
勾上。是for all users
呢还是for current user
你自己看咯。
编码
上个例子是C,那这个就上C++好了。
目录结构
我就不同目录下多文件了啊,那没啥意思。咱上多层级目录多源文件的。
目录结构
代码
Printer.h
#pragma once
class Printer{
public:
void print();
Printer();
~Printer();
};
Printer.cpp
#include "Printer.h"
#include <iostream>
using namespace std;
void Printer::print(){
cout<<"Hello World"<<endl;
}
Printer::Printer() {
cout<<"Printer Object Constructed"<<endl;
}
Printer::~Printer(){
cout<<"Printer Object Destructed"<<endl;
}
- 'main.cpp'
#include "Lib/Printer.h"
int main(){
Printer* printer = new Printer();
printer->print();
delete printer;
return 0;
}
生成项目
编写CMakeLists
每一个包含源文件的目录中都要编写CMakeLists.txt
-
CMakeLists.txt
根目录中的CMakeLists.txt
,一般程序入口(main
函数)在此。
# 使用CMake Tools插件(可选,如果这个项目去到一个没有这个插件的机器也同样可以生成项目)
include(CMakeToolsHelpers OPTIONAL)
# CMake 最低版本号要求
cmake_minimum_required(VERSION 2.8)
# 项目名称
project(CMakeTest)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_ROOT_SRCS变量
aux_source_directory(. DIR_ROOT_SRCS)
# 添加 Lib子目录
add_subdirectory(Lib)
# 指定生成目标
add_executable(CMakeTest main.cpp ${DIR_ROOT_SRCS})
# 添加链接库
target_link_libraries(CMakeTest PrinterLib)
-
Lib/CMakeLists.txt
子目录中的CMakeLists.txt
,一般将子目录中的源文件编译为静态链接库。
include(CMakeToolsHelpers OPTIONAL)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library(PrinterLib ${DIR_LIB_SRCS})
Build
完成上面步骤后,就可以Build了,如果你安装了vector-of-bool.cmake-tools
插件,VSCode左下角的底边栏会有Build
按钮。
点击
Build
后,选择Debug
(为了下一步演示调试,若不调试就Release
)Build Setting
输出框会不停往出喷东西,只要最后输出了
[vscode] cmake exited with return code 0
,就说明Build成功。Build Output
目录结构
目录结构新增加的build文件夹是
vector-of-bool.cmake-tools
插件干的,它做的很好,如果我们手动cmake - make
,那些生成文件会跑的到处都是。其中
build/CMakeTests.exe
就是我们生成的可执行程序。
调试
调试设置
文件
- 首选项
- 设置
在CMake Tools configuration
中找到cmake.debugConfig
,生成设置:
"cmake.debugConfig": {
"miDebuggerPath": "gdb.exe", // Windows 下指定gdb路径(已添加到PATH)
"externalConsole": true, // 使用外部控制台
"stopAtEntry": false // 在起点处停顿(噢!在这停顿!)
},
Start Debugging
藏起来的Debug按钮刚Build完你可能看不到这两个按钮:
调试按钮
其实他们藏起来了... 这应该是个Bug,我已经反馈给作者了。
重启VSCode后他们就会出现,或者直接点那两个藏起来的按钮(还有这种操作?!):先点右边选择调试目标,再点左边开始调试。
合影留念
完结撒花结语
Code Everywhere, Build Everywhere.
这样的搭配是不是很爽呢?除了MinGW是Windows特有的矫情外。其余的在任何平台都是一样的。虽说现在VSCode和上面的大部分插件都运行的不太稳定,但是我还是很喜欢这种组合进行跨平台的轻量级开发的。
FAQ
-
Q: VSCode或编译器找不到头文件?
A: 如果VSCode用绿色波浪线给你划出那些找不到源的代码时,将鼠标停留在波浪线上,然后会出现一个小 灯泡。点“Add include path to settings.”。然后它会自动帮你建立一个名为“c_cpp_properties.json”的文件。
"name": "Mac",
"includePath": [
"${workspaceRoot}",
"/usr/include",
"/usr/local/include"
],
你会在其中找到类似上述代码块的地方,根据“name”后面所描述的系统,在其下的“includePath”中手动添加头文件目录。JSON语法。
-
Q: 为什么路径动不动就写成
${fileDirname}/${fileBasenameNoExtension}.out
?A: 这样做的好处就是可以在同一配置下进行多个单文件的编译开发,不用每次生成和调试的时候都去写配置文件。使用场景嘛,主要就是写写算法题之类的。
我写算法题时的目录结构
.out
后缀名就是自己瞎起的。不同平台的可执行后缀名都不一样,这样写就跟谁都不沾边了... -
Q: 如果我不用
vector-of-bool.cmake-tools
插件,要如何Build?A: 写完
CMakeLists.txt
后,打开VSCode中自带的终端(点击底边栏的输出按钮)
键入:
cmake .
会生成系统对应的项目(如果你是Windows并安装了Visual Studio,他就会生成VS项目)。若想生成
MinGW Makefiles
则键入cmake -G "MinGW Makefiles" .
(注意区分大小写),之后会为你生成makefiles,然后再键入make
(若没制作前面所提到的mingw32-make.exe
的名为make.exe
的副本,则键入mingw32-make
),最终生成可执行文件。
网友评论
Loading CMake Tools from c:\Users\zhuan\Desktop\cm\.vscode\.cmaketools.json
Detected available environment "Visual C++ 14.0 - x86
Detected available environment "Visual C++ 14.0 - amd64
Detected available environment "Visual C++ 14.0 - amd64_arm
Detected available environment "MinGW - C:\TDM-GCC-64
Started new CMake Server instance with PID 9808
Build program for generator Ninja is not found. Skipping...
Build program for generator Unix Makefiles is not found. Skipping...
None of preferred generators available on the system.
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo1)
include_directories("C:/boost_1_64_0")
link_directories("C:/boost_1_64_0/lib")
SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -lboost_system-vc141-mt-1_64")
LINK_LIBRARIES(boost_system-vc141-mt-1_64, boost_filesystem-vc141-mt-1_64)
# 指定生成目标
add_executable(Demo demo.cpp)
demo.o:demo.cpp:(.text+0xce): undefined reference to `boost::system::generic_category()'
demo.o:demo.cpp:(.text+0xd8): undefined reference to `boost::system::generic_category()'
demo.o:demo.cpp:(.text+0xe2): undefined reference to `boost::system::system_category()'
demo.o:demo.cpp:(.text$_ZN5boost16thread_exceptionC2EiPKc[__ZN5boost16thread_exceptionC2EiPKc]+0xa): undefined reference to `boost::system::generic_category()'
demo.o:demo.cpp:(.text$_ZN5boost6detail16thread_data_baseC2Ev[__ZN5boost6detail16thread_data_baseC2Ev]+0xb): undefined reference to `vtable for boost::detail::thread_data_base'
demo.o:demo.cpp:(.text$_ZN5boost6thread12start_threadEv[__ZN5boost6thread12start_threadEv]+0x10): undefined reference to `boost::thread::start_thread_noexcept()'
demo.o:demo.cpp:(.text$_ZN5boost6threadD1Ev[__ZN5boost6threadD1Ev]+0x10): undefined reference to `boost::thread::detach()'
demo.o:demo.cpp:(.text$_ZN5boost6thread4joinEv[__ZN5boost6thread4joinEv]+0x16): undefined reference to `boost::thread::get_id() const'
demo.o:demo.cpp:(.text$_ZN5boost6thread4joinEv[__ZN5boost6thread4joinEv]+0x24): undefined reference to `boost::this_thread::get_id()'
demo.o:demo.cpp:(.text$_ZN5boost6thread4joinEv[__ZN5boost6thread4joinEv]+0x6c): undefined reference to `boost::thread::join_noexcept()'
demo.o:demo.cpp:(.text$_ZN5boost6detail11thread_dataIPFvvEED1Ev[__ZN5boost6detail11thread_dataIPFvvEED1Ev]+0x19): undefined reference to `boost::detail::thread_data_base::~thread_data_base()'