本文参考资料:《视觉SLAM十四讲》
目录
一、新建一个库
二、新建一个CMakeLists.txt文件
三、新建一个头文件
四、新建C++程序
五、使用cmake
当我们刚入门C++的时候,一定会编写一个如下的C++程序:
#include
using namespace std;
void print_Hello();
int main()
{
print_Hello();
return 0;
}
void print_Hello()
{
cout << "Hello!" << endl;
}
然后使用g++编译这个程序,最后输入./a.out运行刚刚编译的程序,输出Hello!。
现在让我们来修改一下这个程序,我们尝试着自己编写头文件以代替#include
在一个C++工程中,并不是所有代码都会编译成可执行文件。只有带main函数的文件才会生成可执行程序,而另一些代码,我们只想把它们打包成一个东西,供其他程序调用,这个东西叫做库(library)。
一个库往往是许多算法、程序的集合,我们要学习如何用cmake生成库,并且使用库中的函数。现在我们演示如何自己编写一个库:
首先在Linux系统中建一个文件夹,命名为C++_project,在这个文件夹下新建一个libhello.cpp文件:
#include
using namespace std;
void printHello()
{
cout << "Hello!" << endl;
}
这个库提供了一个printHello函数,调用此函数将输出一条信息。但是它没有main函数,这意味着这个库里没有可执行文件。
我们在C++_project文件夹下新建一个CMakeLists.txt文件,内容如下:
#声明要求的cmake最低版本
cmake_minimum_required(VERSION 2.8)
#声明一个cmake工程
project(hello)
CMakeLists.txt文件用于告诉cmake要对这个目录下的文件做什么事情。CMakeLists.txt文件的内容需要遵循cmake的语法。根据注释,我们应该理解每句话做了些什么。
我们在CMakeLists.txt里加上如下内容:
add_library(hello libhello.cpp)
这条命令告诉cmake,我们想把这个文件编译成一个叫做“hello” 的库。
在Linux中,库文件分成静态库和共享库两种。静态库以.a作为后缀名,共享库以.so结尾。所有库都是一些函数打包后的集合,差别在于静态库每次被调用都会生成一个副本,而共享库则只有一个副本,更省空间。如果想生成共享库而不是静态库,只需要在CMakeLists.txt中把上一条语句换成以下语句:
add_library(hello_shared SHARED libhello.cpp)
库文件是一个压缩包,里面有编译好的二进制函数。如果仅有.a或.so库文件,那么我们并不知道里面的函数具体是什么,调用的形式又是什么样的。为了让别人(或自己)使用那个库,我们需要提供一个头文件,说明这些库里都有些什么。因此,对库的使用者,只要拿到了头文件和库文件。就可以调用这个库。
下面编写libhello的头文件,命名为libhello.h:
#ifndef LIBHI_H_
#define LIBHI_H_
//上面的宏定义是为了防止重复引用这个头文件而引起的重定义错误
//打印一句hello的函数
void printHello();
#endif
这样,根据这个文件和库文件,就可以使用printHello函数了。
最后,我们新建一个hello.cpp文件,写一个可执行程序来调用这个简单的函数:
#include "libhello.h"
//使用libhello.h中的printHello()函数
int main()
{
printHello();
return 0;
}
然后,在CMakeLists.txt中添加一个可执行的生成命令,链接到刚才使用的库上:
#添加一个可执行程序
#语法:add_executable(程序名 源代码文件)
add_executable(hello hello.cpp)
target_link_libraries(hello hello_shared)
通过这两行语句,hello程序就能顺利使用hello_shared库中的代码了。
当我们编写好头文件、库、CMakeLists.txt文件、C++程序后,我们就要对这个程序进行编译和运行了。
实际上,任何一个C++程序都可以用g++来编译。但是当所写的程序规模越来越大时,一个工程里可能有许多个文件夹和源文件,当我们要编译时,输入的编译命令将越来越长。通常,一个小型的C++项目可能含有十几个类,各类间还存在着复杂的依赖关系。其中一部分要编译成可执行文件,另一部分编译成库文件。如果仅靠g++命令,则需要大量的编译指令,整个编译过程会变得异常繁琐。因此,对于C++项目,使用一些工程管理工具会更加高效。
在一个cmake工程中,我们会用cmake命令生成一个makefile文件,然后用make命令根据这个makefile文件的内容编译整个工程。
现在,在当前目录下,调用cmake对该工程进行cmake编译(指令最后有个句点,这表示在当前目录下进行cmake):
cmake .
cmake会输出一些编译信息,然后在当前目录下生成一些中间文件,其中最重要的就是MakeFile。由于MakeFile是自动生成的,我们不必修改它。现在,用make命令对工程进行编译:
终端输入:
make
在编译过程中会输入一个编译进度。如果顺利通过,我们就可以得到在CMakeLists.txt中声明的那个可执行文件hello。执行它:
./hello
终端会输出Hello!。
这次我们使用了先执行cmake再执行make的做法,执行cmake的过程处理了工程文件之间的关系,而执行make过程实际调用了g++来编译程序。虽然这个过程中多了调用cmake和make的步骤,但我们对项目的编译管理工作从输入一串g++命令,变成了维护若干个比较直观的CMakeLists.txt文件,这将明显降低维护整个工程的难度。例如,如果想新增一个可执行文件,只需在CMakeLists.txt中添加一行“add_executable”命令即可,而后续的步骤是不变的。cmake会帮我们解决代码的依赖关系,无需输入一大串g++命令。
在这个过程中唯一让我们不满的是,cmake生成的中间文件还留在我们的代码文件中。当我们想要发布代码时,我们并不希望把这些中间文件一同发布出去。这时我们还需要把它们一个一个地删除。一种更好的做法是让这些中间文件都放在一个中间目录中,在编译成功后,把这个中间目录删除即可。所以,更常见的的编译cmake工程的做法如下:
mkdir build
cd build
cmake ..
make
我们新建了一个中间文件夹“build”,然后进入build文件夹,通过cmake ..命令对上一层文件夹也就是代码所在文件夹进行编译。这样,cmake产生的中间文件就会生成在build文件夹中,与源代码分开。当发布源代码时,只要把build文件夹删了就行。
现在,简单回顾一下我们之前做了什么:
1、程序代码由头文件和源文件组成;
2、带有main函数的源文件编译成可执行文件,其他的编译成库文件;
3、如果可执行程序想调用库文件中的函数,则它需要参考该库提供的头文件,以明白调用的格式,同时,要把可执行程序链接到库文件上。