机器人SLAM导航:核心技术与实战
上QQ阅读APP看书,第一时间看更新

2.2 C++代码的编译方法

本书使用Linux系统进行代码开发,所以这里讨论一下C++代码在Linux中的编译方法。由于本书需要用到ROS框架,因此我们将在Ubuntu系统中讲解具体案例。如果还没有安装Ubuntu系统,请参考网上资料自行安装。

这里以一个简单的C++工程为例,分别讲解g++、make和CMake这3种编译工具的用法。先来构建这个C++工程。新建一个文件夹demo,在demo中新建3个文件,分别命名为main.cpp、foo.h和foo.cpp。首先在foo.h里面声明类,内容如代码清单2-1所示。

代码清单2-1 foo.h文件的内容

 1 #ifndef FOO_H_
 2 #define FOO_H_
 3 
 4 #include <string>
 5 
 6 namespace foo
 7 {
 8   class MyPrint
 9   {
10   public:
11     MyPrint(std::string output);
12     void ExcutePrint();
13 
14     std::string output_;
15   };
16 }
17 #endif

第1行、第2行和第17行是宏定义,防止头文件的重复包含与编译。

第6行是命名空间设置,防止出现重复的函数和变量名称。命名空间在C++中使用很普遍,它能够提高代码的可读性。

第8~15行声明类,这里并不对类的函数进行具体实现,函数的具体定义将放在对应的*.cpp文件中。其中第11行是构造函数,带一个字符串类型的参数。第12行是执行打印的成员函数。

第14行是存储打印内容的成员变量。

然后是foo.cpp,在里面对类的函数进行具体实现,内容如代码清单2-2所示。

代码清单2-2 foo.cpp文件的内容

 1 #include "foo.h"
 2 #include <iostream>
 3 #include <string>
 4 
 5 namespace foo
 6 {
 7   MyPrint::MyPrint(std::string output):output_(output)
 8   {
 9     std::cout<<"class MyPrint created a object!";
10     std::cout<<std::endl;
11   }
12  
13   void MyPrint::ExcutePrint()
14   {
15     std::cout<<output_<<std::endl;
16   }
17 }

第1行,配套的头文件必须包含。

第7~11行,构造函数的具体实现,函数头部需要使用类名作为作用域,函数的传入参数用来为类成员变量output_赋值,函数体内执行打印信息,表示该类已经被实例化。

第13~16行,执行打印的函数的具体实现。

最后是main.cpp,在里面对类进行实例化,并调用类中的成员函数实现打印,内容如代码清单2-3所示。

代码清单2-3 main.cpp文件的内容

 1 #include "foo.h"
 2 
 3 int main(int argc, char** argv)
 4 {
 5   foo::MyPrint my_print("I can output string!");
 6   my_print.ExcutePrint();
 7   return 0;
 8 }

第1行,包含foo.h头文件,以便使用里面的类MyPrint。

第5行,创建类MyPrint的实例对象,并传入初始化参数。

第6行,调用对象中的成员函数,执行打印操作。

2.2.1 使用g++编译代码

在Linux系统中,采用g++编译C++代码是最直接的。编译过程分成4步,分别是预处理、编译、汇编和连接,编译过程如图2-3所示。

0

图2-3 g++编译过程

先安装g++工具,打开命令行终端,输入如下命令。

sudo apt install g++ 

安装好g++后,就可以对demo工程进行编译了,编译命令如下。

cd demo/
g++ foo.cpp main.cpp -o demo

上面的编译命令执行后,将对foo.cpp和main.cpp进行编译,最后生成可执行文件demo,直接使用下面的命令便可以运行可执行文件demo。

./demo

2.2.2 使用make编译代码

当程序非常庞大时会涉及很多个*.cpp和外部依赖库,逐一输入g++的命令将很不方便。这时候就可以使用makefile文件来编写编译脚本,然后使用make命令进行编译。

将上面的编译命令改写成makefile的形式,在文件夹demo中新建一个文件makefile,文件内容如代码清单2-4所示。

代码清单2-4 makefile文件的内容

 1 start:
 2   g++ -o foo.o -c foo.cpp
 3   g++ -o main.o -c main.cpp
 4   g++ -o demo foo.o main.o
 5 clean:
 6   rm -rf foo.o main.o

第1行和第5行,是命令块的命名,makefile中的命令可以划分成不同的块,默认make命令是调用第一个块的命令。make命令后面接命令块的名称,可以调用相应块的命令。

第2~4行,分别对各个*.cpp文件进行编译,最后将生成的目标文件*.o连接即可得到可执行文件demo。这里要注意,命令前面必须使用tab进行缩进。

第6行,删除编程产生的中间文件,从这里可以看出makefile中的命令是完全兼容Linux命令的。

在命令行终端用make命令编译试试。

cd demo/
make

编译完成后,目录下会生成一些中间文件,用make clean命令清除这些中间文件。

make clean 

清除完中间文件后,就只剩下可执行文件demo了,直接用下面的命令便可以运行可执行文件demo。

./demo

2.2.3 使用CMake编译代码

虽然makefile已经大大降低了大型程序的编译难度,但是程序有众多依赖和关联,如果全部手动去维护这些依赖关系还是很麻烦的。CMake可以自动处理程序之间的关系,并产生对应的makefile文件,然后调用make就能轻松编译了。

将上面的编译改成CMake的方式,在文件夹demo中新建一个文件CMakeLists.txt,文件内容如代码清单2-5所示。

代码清单2-5 CMakeLists.txt文件的内容

 1 cmake_minimum_required (VERSION 2.8)
 2 project(demo)
 3 
 4 include_directories("${PROJECT_BINARY_DIR}")
 5 
 6 add_library(foo foo.cpp)
 7 add_executable (demo main.cpp)
 8 target_link_libraries (demo foo)

第1行,声明CMake最低要求的版本。

第2行,声明CMake的工程名。

第4行,设置头文件搜索路径。

第6行,创建库文件。

第7行,创建可执行文件。

第8行,为可执行文件连接依赖库。

在命令行终端用CMake命令编译,命令执行后,目录下会产生大量的中间文件和一个Makefile文件。继续用make命令编译,就可以得到可执行文件demo。

cd demo/
cmake .
make

编译完成后,直接用下面的命令便可以运行可执行文件demo。

./demo

文件CMakeLists.txt的编写需要遵循CMake的语法,想要了解更多CMake语法的细节,可以访问CMake的wiki页面[1]

不难发现,第1章中讲过的ROS功能包也是采用CMake方式编译的,只不过ROS对CMake做了进一步的封装,即catkin_make。


[1]参见https://cmake.org/Wiki/CMake