1.推荐文件目录
即一个C++工程文件中包含.vscode文件夹、build文件夹、include文件夹、src文件夹以及一个CMakeLists.txt文件。
2. .vscode文件夹
.vscode文件夹一般应该包含三个配置文件:c_cpp_properties.json、tasks.json、launch.json
c_cpp_properties.json是用于配置编译器环境的,这里最重要的就是includePath,其指明了C/C++标准库、用户头文件所在位置。如果我们使用的库不在includePath里面,我们需要手动添加。这样的话不需要CmakeLists.txt文件也可以编写C/C++。
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/include/**",
"/usr/local/**",
"/usr/lib/**"
],//头文件路径
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
tasks.json主要作用是添加构建(编译、链接等)任务。也就是说要想编译、链接生成可执行文件,必须要配置这个文件。当然了,后面说到的CMakeLists.txt可以替代这个文件。
创建tasks.json的文件的方法如下:
首先,按住ctrl+shift+p打开命令行,如下图所示
在输入框输入:
Tasks: Run task
出现如下提示:
回车,然后出现:
继续回车:
选择最下面Others选项,会生成默认的task.json。
以下是我们常用到的task.json文件。label一般我们都设置为build,command即选择什么编译器进行编译,一般选gcc或g++,args为编译的时候用到的参数,具体可参见https://blog.csdn.net/yfldyxl/article/details/81389543
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "g++",
"args":["-g", "${file}", "-std=c++11", "-o", "${fileBasenameNoExtension}.out"]
}
]
}
launch.json主要是调试时的配置文件,也就是说如果我们单纯只是想生成可执行文件,其实这个文件没有也可以。
创建launch.json的文件的方法如下:
点击左侧下面第4个类似于一个虫子的图标,即DEBUG
然后点击蓝色的字体create a launch.json file:
选择第一个C++(GDB/LLDB)即可生成一个默认的launch.json文件。
以下是我们常用到的launch.json文件。这里面有两个参数比较重要,一个是program参数,其为生成的可执行文件名,一定要与tasks.json文件中指定生成的文件名相同。还有一个是preLaunchTask,一般也要与tasks.json文件中的label一致,一般都是build。如果采用CMakeLists.txt的方法,preLaunchTask参数就不用指定了,删除这个参数就行。但是program参数需要指定,指定的方式为绝对路径名,例如:/home/zpp/work_ws/hello/build/hello
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "gcc build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}.out",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "build",
"miDebuggerPath": "/usr/bin/gdb"
}
]
}
3.build文件夹
主要用来存放生成的Makefile以及可执行文件。
4.include文件夹
主要用来存放我们自己编写的头文件
5.src文件夹
主要用来存放我们自己写的c/cpp文件
6.CMakeLists.txt
这里说一下,其实对于一个庞大的C++工程,我们可以不必写上面的.vscode文件夹,因此也就不必写那3个json文件。原因在于一个庞大的C++工程如果配置tasks.json然后用gcc或g++来编译,太过麻烦,他们更适合单一的文件编译,而通常C++工程通常较为庞大,因此更推荐编写CMakeLists.txt。
首先来看由CMakeLists.txt到生成可执行文件所经历的步骤。
首先,我们需要知道make工具,它是一个自动化编译工具,用来批处理编译源文件,但是使用make工具进行编译需要编写一个规则文件,make依据它来批处理编译,这个文件就是Makefile,但是对于一个大工程,编写Makefile也是一件很复杂的事情,所以这时人们设计出cmake工具,它能够生成Makefile文件,随之而来的就是CMakeLists.txt文件,它是cmake所依据的规则,相对来说,CMakeLists.txt文件的编写要简单不少。
以下是利用libevent库编写的客户端与服务器工程bufferevent的CMakeLists.txt文件。
# 最低CMake版本要求
cmake_minimum_required(VERSION 3.5.1)
# 项目名称
project(bufferevent)
#添加debug信息,使得生成的可执行文件可以调试
#set(CMAKE_BUILD_TYPE DEBUG)
#使用通配符添加多个源文件
#file(GLOB SRC_LIST "src/*.c")
#编译选项
add_compile_options(-std=c++11)
# 头文件路径
include_directories("include")
#链接库
link_libraries(event)
# 生成可执行的文件
add_executable(server src/server.c)
add_executable(client src/client.c)
也就是说,CMakeLists.txt文件具有c_cpp_properties.json、tasks.json两个文件的功能,一般如果C++工程较小,可以编写tasks.json文件,否则更适合编写CMakeLists.txt文件。不过我还是推荐我们每次编写程序的时候编写一下c_cpp_properties.json,因为该文件中includePath参数可以很好指定头文件目录,这样就算没有CMakeLists.txt文件,我们在vscode编写程序的时候它也自动包含了头文件,这样就可以具有代码提示功能。而CMakeLists.txt中的库文件路径并不是太好指定(主要我目前对CMakeLists.txt还不是太了解)。如果我们需要在VS里调试的话,还是需要编写launch.json文件的。
写好CMakeLists.txt,需要进行编译并生成可执行文件,以下是步骤,假设工程目录为work_ws/bufferevent.
首先进入build文件夹
cd work_ws/bufferevent/build
接下来生成makefile文件,..表示上一级目录,也就是CMakeLists.txt所在目录
cmake ..
编译makefile文件,生成可执行文件
make
如果我们嫌每次都要输入cmke..,然后在make有点繁琐,我们可以自己编写sh文件放在build文件夹下,在sh文件里输入我们要执行的相关指令。如下,是我在build文件夹下创建的make.sh文件。
#开启扩展模式匹配 否则识别不了下面的!()
shopt -s extglob
#删除当前文件夹下除了make.sh之外的所有文件
rm -rf !(make.sh)
#生成Makefile
cmake ..
#根据Makefile生成编译生成可执行文件
make
#删除中间产生的编译文件
rm -rf CMakeFiles CMakeCache.txt cmake_install.cmake Makefile
这样,每次我只要进入工程下的build文件夹,输入
sh make.sh
即可编译生成可执行文件。
CMakeLists.txt文件常用命令讲解
以我目前的理解,对CMakeLists.txt文件中几个常用的命令进行讲解(不一定是对的),之中可能会穿插讲解一些其他的东西。
头文件和库文件的区别
先讲解一些头文件和库文件的区别。我们知道,C或者C++代码的编译过程可以分为4步:预处理(需要头文件)->编译->汇编->链接(需要库文件),在程序执行时可能还有动态链接的过程。头文件的主要作用是提供声明,如我们平常编写的h文件就属于头文件,在编译的时候,在预处理阶段利用头文件的声明就足够了。而库文件主要提供定义/实现。在链接的时候,把已经编译好的目标文件(常见的如.o文件,在编译过程的第三阶段汇编完以后产生)和现有的库文件(常见的如.lib和.a静态库文件,.dll和.so动态库文件)进行链接,生成可执行文件。库文件是二进制的,在库文件中是看不到原始的源代码的。库和可执行文件的区别是,库不是独立程序,他们是向其他程序提供服务的代码。 当然使用库文件的好处不仅仅是对源代码进行保密,使用库文件还可以减少重复编译的时间,增强程序的模块化。将库文件链接到程序中,有两种方式,一种是静态链接库,另一种是动态链接库。
静态库文件和动态库文件
库文件又分为静态库文件和动态库文件,常见的.lib文件(windows)和.a文件(linux)是静态库文件, 编译的时候二进制库文件直接加载到内存(可以简单理解为把代码段直接插入到程序中) 。常见的.dll文件(windows)和.so文件(linux)是动态库文件,编译的时候,只是产生一些调用动态库文件代码的导入表,在执行到需要的地方时再加载到内存,不需要的时候再释放这片内存。静态库由于在编译的时候直接将库文件加载到内存,在执行的时候就不需要加载,因而其执行速度快,但是耗内存空间。动态库由于在程序运行时才将需要的库文件加载到内存,不需要的时候就释放内存,因而其省内存空间,但是执行速度慢。
linux系统默认头文件搜索路径
在终端运行下述命令可以查看linux系统默认C++程序头文件搜索路径:
cpp -v
运行结果:
可以看到这是linux系统默认的头文件搜索路径,默认的意思是说每次编译的时候即使我们不指定头文件目录,系统也会自动搜索这几个目录看看有没有我们需要的头文件。但是如果我们需要用到的头文件不在这几个目录当中,我们需要手动指定。对于gcc来说,主要使用-I(大写的i)命令(对应include),对CMakeLists.txt文件,主要使用include_directories()命令。对于C++工程来说,常需要我们手动添加的头文件目录主要是我们自己编写的h文件所在目录。
linux系统头文件搜索路径顺序为:
- 搜索当前目录
- 如果采用gcc编译器,搜索-I(大写的i)指定的目录,如果采用make编译器,搜索CMakeLists.txt中include_directories()指定的目录
- 搜索环境变量CPLUS_INCLUDE_PATH(C程序使用的是C_INCLUDE_PATH)中指定的目录。(可以在终端输入 echo $CPLUS_INCLUDE_PATH,一般刚开始没有设置的时候为空)
- 搜索系统默认的目录,即刚刚上面看到的那几个目录
linux系统默认库文件搜索路径
linux系统库文件搜索路径顺序为:
- 编译目标代码时指定的库文件搜索路径,如果是gcc编译器,搜索-L指定的目录(对应link),如果是make编译器,搜索CMakeLists.txt中link_directories()指定的目录
- 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
- 配置文件/etc/ld.so.conf中指定的动态库搜索路径
- 默认的动态库搜索路径/lib;/usr/lib;/usr/local/lib
关于linux系统中C/C++ 头文件以及库文件的搜索路径,详细请参考https://www.cnblogs.com/answercard/p/7763299.html
gcc -I(大写i) -L -l(小写l)区别
-I(大写i):
格式:-IDIRECTORY
作用:指定额外的头文件搜索路径
例如:
gcc -o hello hello.c -I/home/zpp/work_ws/hello/include
即除了系统默认的头文件路径,我们添加额外的头文件搜索路径:/home/zpp/work_ws/hello/include
-L:
格式:-LDIRECTORY
作用:指定额外的动态库文件搜索路径DIRECTORY。
例如:
gcc -o hello hello.c -L/home/zpp/work_ws/hello/lib
即除了系统默认的库文件路径,我们添加额外的库文件搜索路径:/home/zpp/work_ws/hello/lib
-l(小写的l):
格式:-lLIBRARY
作用:链接时搜索指定的动态库库名
例如:
gcc -o hello hello.c -lworld
即指定链接时,链接libworld.so文件,如果gcc编译选项加入了-static表示寻找libworld.a静态库文件。注意-l参数紧接着的LIBRARY表示库名,库名和库文件名有这样的关系,以libworld.so文件来说,它的库名是world,库文件名是libworld.so,很容易看出,把库文件名的头lib和尾.so去掉就是库名了。另外,我们需要知道,这个libworld.so文件就在我们刚刚-L参数指定的L/home/zpp/work_ws/hello/lib路径中。那么-L与-l(小写的l)的区别就是,-L指定库文件目录,而-l指定库名。那么编译的时候什么时候要用到-L参数,什么时候用到-l参数,还是说两者要一起使用。答案是这样的,如果我们在程序中用到的库是C/C++标准库,如标准io库(如printf)和标准模板库,那么我们-L和-l参数都不需要指定。如果我们用到了非标准库,且该库所在目录在系统默认的库文件目录中,我们可以不用-L指定库文件目录,但必须要用-l指定库名。如果我们用到了非标准库,且该库所在目录不在系统默认的库文件目录中,我们需要同时使用-L指定库文件目录,用-l指定库名。有人可能会问,为什么我们已经指定了库文件目录,还需要指定库名,为什么就不能像头文件一样只要指定头文件目录就可以了呢?原因在于,系统默认的库文件目录下有很多库,系统默认只会载入C/C++标准库,非标准库是不会默认载入的,即使它在默认的库目录中,因为都加载的话程序就太大了。系统不会自动去查询默认库目录中的所有库,谁知道系统中有多少库呢?至于头文件为什么指定路径就可以,我觉得一个显而易见的原因就是程序中已经指明了要哪些头文件,所以系统在对应目录搜索一下就OK了,完全不费什么事,而库文件显然不具备这个特性。关于为什么要指定链接库原因详见https://blog.csdn.net/lee244868149/article/details/38707127
CMakeLists.txt常用命令
在理解了上面的基础只是以后,理解CMakeLists.txt的常用指令就很简单了。
include_directories(dir1 dir2...)
#添加额外的头文件目录 相当于gcc的 -Idir1 dir2...
#如果除了系统默认的头文件目录之外,没有程序额外需要的头文件目录,可以不写
link_directories(dir1 dir2...)
#添加额外的库文件目录 相当于gcc的 -Ldir1 dir2...
#如果除了系统默认的库文件目录之外,没有程序额外需要的库文件目录,可以不写
link_libraries(lib1 lib2...)
#设置所有目标需要链接的库 相当于gcc的 -llib1 lib2...
#如果除了标准库之外,没有其他非标准库,可以不写,反之link_libraries或target_link_libraries二选一
#注意这个命令必须用在add_executable()命令之前
target_link_libraries(targetname lib1 lib2...)
#设置单个目标需要链接的库 相当于gcc的 -llib1 lib2...
#如果除了标准库之外,没有其他非标准库,可以不写,反之link_libraries或target_link_libraries二选一
#注意这个命令必须用在add_executable()命令之后
add_executable(abcd src/abcd.c)
#将abcd.c编译生成可执行文件abcd
add_library(abcd STATIC src/abcd.cpp)
#添加自己编写的库文件,#STATIC表示生成静态库abcd.a文件,SHARED表示生成静态库abcd.so文件
关于CMakeLists.txt的详细信息,参见博客http://blog.sina.com.cn/s/blog_66439a3e0101ieqv.html,写的真的非常好。
另外,经过自己的不断学习,现在写了一份更加便捷的CMakeLists.txt,只要遵循之前的目录结构,这个CMakeLists.txt在我们添加源文件的时候也不需要改动任何代码,可以自动生成工程文件名、可执行文件。需要指定的就是需要连接的动态链接库,一般小型程序也不用指定。还有就是这个工程到底是根据多个源文件生成一个可执行文件还是为每一个源文件都生成一个可执行文件。下面的CMakeLists.txt已经默认写好了,每次我们只需要根据自己的实际情况将选择即可。
# 最低CMake版本要求
cmake_minimum_required(VERSION 3.5.1)
#使用通配符添加多个源文件名列表赋给SRC_LIST
file(GLOB SRC_LIST "src/*.c")
#将src工作目录的绝对路径赋给SRC_DIR
file(GLOB SRC_DIR "src")
#将src的前一级目录所在的文件夹名赋给project_name 也就是工程文件夹名作为工程名
#.表示匹配任意一个字符 *表示其左边的字符被匹配0次或多次 .*结合起来就是匹配任意多个字符
#()表示保存匹配的表达式并随后替换它 \\1表示之前匹配到的第1个()内的内容
string(REGEX REPLACE ".*/(.*)/src" "\\1" project_name ${SRC_DIR})
# 项目名称
project(${project_name})
#添加debug信息,使得生成的可执行文件可以调试
#set(CMAKE_BUILD_TYPE DEBUG)
#编译选项
add_compile_options(-std=c++11)
# 头文件路径
include_directories("include")
#链接库
#link_libraries(event)
#为每一个源文件生成一个可执行文件 可执行文件名即为.c前缀之前的源文件名
foreach(src ${SRC_LIST})
string(REGEX REPLACE ".*/(.*).c" "\\1" program ${src})
add_executable(${program} ${src})
endforeach(src)
#将所有源文件生成一个可执行文件
#add_executable(${project_name} ${SRC_LIST})