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所示。
图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。