视觉十四讲 开篇
一:编译器 g++ [ 简单例子的首选 ]
能够将一个 .cpp 程序编译成一个可执行文件;
g++ 默认把源文件编译成 a.out 这个名字的程序
先用编辑器输入了 helloSLAM.cpp的源代码,g++编译器对源码进行编译
在.cpp文件通一目录下,右击空白 选中“在终端打开(T)”,打开终端界面并输入:
g++ helloSLAM.cpp
如果顺利的话,这条命令应该没有任何输出。如果机器上出现“command not found”的错误,说明你可能还没有安装 g++,请用安装g++:
sudo apt-get install g++
刚才这条编译命令把 helloSLAM.cpp 这个文本文件编译成了一个可执行程序。我们检查当前目录,会发现多了一个 a.out 文件,而且它具有执行权限(终端里颜色不同)。我们输入./a.out 即可运行此程序:
% ./a.out
Hello SLAM!
如我们所想,这个程序输出“Hello SLAM!”,告诉我们它在正确运行。
二:使用 cmake 来管理源代码 [ 对于大工程项目 ]
在一个 cmake 工程中, 使用 cmake 命令生成一个 makefile 文件, 然后, 用 make命令, 根据这个
makefile 文件的内容,编译整个工程。
首先在 .cpp文件同目录下 新建一个 CMakeLists.txt 文件 ,填入以下内容:
# 声明要求的 cmake 最低版本
cmake_minimum_required( VERSION 2.8 )
# 声明一个 cmake 工程
project( HelloSLAM )
# 添加一个可执行程序
# 语法:add_executable( 程序名 源代码文件 )
add_executable( helloSLAM helloSLAM.cpp )
每个 CMakeLists.txt 文件, 告诉 cmake 我们要对这个目录下的文件做什么事情。CMakeLists.txt 文件内容需要遵守 cmake 的语法。这个示例中,我们演示了最基本的工程:指定一个工程名和一个可执行程序。根据注释, 应该理解每句话做了些什么。
现在,在当前目录下(slambook/ch2/),调用 cmake 对该工程进行分析:
cmake .
cmake 会输出一些编译器等信息,然后在当前目录下生成一些中间文件,其中最重要的就是 MakeFile 。由于 MakeFile 是自动生成的,我们不必修改它。
生成:CMakeFiles 文件夹 、CMakeCache.txt 、cmake_install.cmake 、 Makefile
现在,用 make 命令对工程进行编译:
make
//以下均为自动输出
Scanning dependencies of target helloSLAM
[100%] Building CXX object CMakeFiles/helloSLAM.dir/helloSLAM.cpp.o
Linking CXX executable helloSLAM
[100%] Built target helloSLAM
编译过程中会输出一个编译进度。如果顺利通过,我们就得到在 CMakeLists.txt 声明的那个可执行程序 helloSLAM。
执行它:
./helloSLAM
//以下均为自动输出
Hello SLAM!
cmake 过程处理了工程文件之间的关系,而 make 过程实际调用了 g++ 来编译程序。
虽然这个过程中多了调用cmake 和 make 的步骤,但我们对项目的编译管理工作,从输入一串 g++ 命令,变成了维护若干个比较直观的 CMakeLists.txt 文件,这将明显降低维护整个工程的难度。比如,当我想新增一个可执行文件时,只需在 CMakeLists.txt 中添加一行“add_executable”命令即可,而后续的步骤都是不变的。cmake 会帮我们解决代码的依赖关系,无需我们输入一大串 g++ 命令。
三: 优化cmake 管理源代码 [ 对于大工程项目 ]
现在这个过程中,唯一让我们不满的是 , cmake生成的中间文件还留在我们代码文件当中。当我们想要发布代码时,并不希望把这些中间文件一同发布出去。这时我们还需把它们一个个删除,这十分的不便。一种更好的做法是 让这些中间文件都放在一个中间目录中 , 在编译成功后,把这个中间目录删除即可。所以,更常见的编译 cmake 工程的做法就是这样:
mkdir build // 在.cpp同目录下新建一个文件夹 build
cd build // 通过cd 进入到build文件夹
cmake .. // 执行cmake
make //
我们新建了一个中间文件夹 “build” , 然后进入 build 文件夹 , 通过 cmake … 命令 , 对上一层文件夹,也就是代码所在的文件夹进行编译。这样 , cmake 产生的中间文件就会生成在 build 文件夹中 , 与源代码分开。当我们发布源代码时 , 只要把 build 文件夹删掉即可。
四: 使用库
在一个 C++ 工程中, 并不是所有代码都会编译成可执行文件。只有带有 main 函数的文件才会生成可执行程序。而另一些代码,我们只想把它们打包成一个东西, 供其他程序调用。这个东西叫做库。一个库往往是许多算法、程序的集合, 我们会在之后的练习中,接触到许多库。OpenCV 库提供了许多计算机视觉相关的算法,而 Eigen 库提供了矩阵代数的计算。
因此,我们要学习如何用 cmake 生成库,并且使用库中的函数。现在我们书写一个 libHelloSLAM.cpp 文件:
//libHelloSLAM.cpp 这是一个库文件
#include <iostream>
using namespace std;
void printHello()
{
cout<<"Hello SLAM"<<endl;
}
这个库提供了一个 printHello 函数,调用此函数将输出一个信息。但是它没有 main函数,这意味着这个库中没有可执行文件。我们在 CMakeLists.txt 里加一句:
add_library( hello libHelloSLAM.cpp )
这条命令告诉 cmake,我想把这个文件编译成一个叫做“hello”的库。然后,和上面一样,使用 cmake 编译整个工程:
cd build
cmake ..
make
这时,在 build 文件夹中会生成一个 libhello.a 文件, 这就是我们得到的库。
在 Linux 中, 库文件分成静态库和共享库两种 。静态库以.a 作为后缀名, 共享库以.so结尾。所有库都是一些函数打包后的集合, 差别在于静态库每次被调用都会生成一个副本, 而共享库则只有一个副本, 更省空间。如果我们想生成共享库而不是静态库,只需用:
add_library( hello_shared SHARED libHelloSLAM.cpp )
就可以编译成一个共享库。此时得到的文件是 libhello_shared.so 了。
库文件是一个压缩包, 里头带有编译好的二进制函数。不过,仅有.a 或.so 库文件的话, 我们并不知道它里头的函数到底是什么,调用的形式又是什么样的。为了让别人(或者自己)使用这个库, 我们需要提供一个头文件, 说明这些库里都有些什么。因此, 对于库的使用者, 只要拿到了头文件和库文件, 就可以调用这个库了。下面我们来写 libhello 的头文件。
//libHelloSLAM.h 库文件的头文件
#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
void printHello();
#endif
这样, 根据这个文件和我们刚才编译得到的库文件,就可以使用这个函数了。下面我们写一个可执行程序, 调用这个简单的函数:
//useHello.cpp 带有main的可执行文件
#include "libHelloSLAM.h"
int main( int argc, char** argv )
{
printHello();
return 0;
}
然后, 在 CMakeLists.txt 中添加一个可执行程序的生成命令, 链接到刚才我们使用的库上:
add_executable( useHello useHello.cpp )
target_link_libraries( useHello hello_shared )
通过这两句话, useHello 程序就能顺利使用 hello_shared 库中的代码了。这个小例子演示了如何生成并调用一个库。请注意对于他人提供的库, 我们也可用同样的方式对它们进行调用,整合到自己的程序中。
总结:
- 首先, 程序代码由头文件和源文件组成;
- 带有 main 函数的源文件编译成可执行程序, 其他的编译成库文件。
- 如果可执行程序想调用库文件中的函数, 它需要参考该库提供的头文件, 以明白调用的格式。同时, 要把可执行程序链接到库文件上。