SLAM算法与工程实践——CMake使用(4)

SLAM算法与工程实践系列文章

下面是SLAM算法与工程实践系列文章的总链接,本人发表这个系列的文章链接均收录于此

SLAM算法与工程实践系列文章链接


下面是专栏地址:

SLAM算法与工程实践系列专栏


文章目录


前言

这个系列的文章是分享SLAM相关技术算法的学习和工程实践


SLAM算法与工程实践------CMake使用(4)

日志

在 CMake 中可以用用户显示一条消息,该命令的名字为 message:

cmake 复制代码
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
  • (无) :重要消息
  • STATUS :非重要消息
  • WARNING:CMake 警告,会继续执行
  • AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
  • SEND_ERROR:CMake 错误,继续执行,但是会跳过生成的步骤
  • FATAL_ERROR:CMake 错误,终止所有处理过程

CMake 的命令行工具会在 stdout 上显示 STATUS 消息,在 stderr 上显示其他所有消息。CMake 的 GUI 会在它的 log 区域显示所有消息。

CMake 警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。

cmake 复制代码
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

变量操作

追加

有时候项目中的源文件并不一定都在同一个目录中,但是这些源文件最终却需要一起进行编译来生成最终的可执行文件或者库文件。如果我们通过 file 命令对各个目录下的源文件进行搜索,最后还需要做一个字符串拼接的操作,关于字符串拼接可以使用 set 命令也可以使用 list 命令。

使用 set 拼接

如果使用 set 进行字符串拼接,对应的命令格式如下:

cmake 复制代码
set(变量名1 ${变量名1} ${变量名2} ...)

关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖。

cmake 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
# 追加(拼接)
set(SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
message(STATUS "message: ${SRC_1}")
使用 list 拼接

如果使用 list 进行字符串拼接,对应的命令格式如下:

cmake 复制代码
list(APPEND <list> [<element> ...])

list 命令的功能比 set 要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND 表示进行数据追加,后边的参数和 set 就一样了。

cmake 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
# 追加(拼接)
list(APPEND SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
message(STATUS "message: ${SRC_1}")

在 CMake 中,使用 set 命令可以创建一个 list。一个在 list 内部是一个由分号; 分割的一组字符串。例如,set(var a b c d e) 命令将会创建一个 list:a;b;c;d;e,但是最终打印变量值的时候得到的是 abcde。

cmake 复制代码
set(tmp1 a;b;c;d;e)
set(tmp2 a b c d e)
message(${tmp1})
message(${tmp2})

输出的结果:

bash 复制代码
abcde
abcde

字符串移除

我们在通过 file 搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,比如:

bash 复制代码
$ tree
.
├── add.cpp
├── div.cpp
├── main.cpp
├── mult.cpp
└── sub.cpp

0 directories, 5 files

在当前这么目录有五个源文件,其中 main.cpp 是一个测试文件。如果我们想要把计算器相关的源文件生成一个动态库给别人使用,那么只需要 add.cpp、div.cp、mult.cpp、sub.cpp 这四个源文件就可以了。此时,就需要将 main.cpp 从搜索到的数据中剔除出去,想要实现这个功能,也可以使用 list

cmake 复制代码
list(REMOVE_ITEM <list> <value> [<value> ...])

通过上面的命令原型可以看到删除和追加数据类似,只不过是第一个参数变成了 REMOVE_ITEM

cmake 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp)
# 移除前日志
message(STATUS "message: ${SRC_1}")
# 移除 main.cpp
list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp)
# 移除后日志
message(STATUS "message: ${SRC_1}")

可以看到,在第8行把将要移除的文件的名字指定给 list 就可以了。但是一定要注意通过 file 命令搜索源文件的时候得到的是文件的绝对路径(在 list 中每个文件对应的路径都是一个 item,并且都是绝对路径),那么在移除的时候也要将该文件的绝对路径指定出来才可以,否是移除操作不会成功。

关于 list 命令还有其它功能,但是并不常用,在此就不一一进行举例介绍了

(1)获取 list 的长度。

cmake 复制代码
list(LENGTH <list> <output variable>)
  • LENGTH:子命令 LENGTH 用于读取列表长度
  • <list>:当前操作的列表
  • <output variable>:新创建的变量,用于存储列表的长度。

(2)读取列表中指定索引的的元素,可以指定多个索引

cmake 复制代码
list(GET <list> <element index> [<element index> ...] <output variable>)
  • <list>:当前操作的列表

  • <element index>:列表元素的索引

    • 从 0 开始编号,索引 0 的元素为列表中的第一个元素;
    • 索引也可以是负数,-1 表示列表的最后一个元素,-2 表示列表倒数第二个元素,以此类推
    • 当索引(不管是正还是负)超过列表的长度,运行会报错
  • <output variable>:新创建的变量,存储指定索引元素的返回结果,也是一个列表。

(3)将列表中的元素用连接符(字符串)连接起来组成一个字符串

cmake 复制代码
list (JOIN <list> <glue> <output variable>)
  • <list>:当前操作的列表
  • <glue>:指定的连接符(字符串)
  • <output variable>:新创建的变量,存储返回的字符串

(4)查找列表是否存在指定的元素,若果未找到,返回 - 1

cmake 复制代码
list(FIND <list> <value> <output variable>)
  • <list>:当前操作的列表
  • <value>:需要再列表中搜索的元素
  • <output variable>:新创建的变量
    • 如果列表 <list> 中存在 <value>,那么返回 <value> 在列表中的索引
    • 如果未找到则返回 - 1。

(5)将元素追加到列表中

cmake 复制代码
list (APPEND <list> [<element> ...])

(6)在 list 中指定的位置插入若干元素

cmake 复制代码
list(INSERT <list> <element_index> <element> [<element> ...])

(7)将元素插入到列表的 0 索引位置

cmake 复制代码
list (PREPEND <list> [<element> ...])

(8)将列表中最后元素移除

cmake 复制代码
list (POP_BACK <list> [<out-var>...])

(9)将列表中第一个元素移除

cmake 复制代码
list (POP_FRONT <list> [<out-var>...])

(10)将指定的元素从列表中移除

cmake 复制代码
list (REMOVE_ITEM <list> <value> [<value> ...])

(11)将指定索引的元素从列表中移除

cmake 复制代码
list (REMOVE_AT <list> <index> [<index> ...])

(12)移除列表中的重复元素

cmake 复制代码
list (REMOVE_DUPLICATES <list>)

(13)列表翻转

cmake 复制代码
list(REVERSE <list>)

(14)列表排序

cmake 复制代码
list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
  • COMPARE:指定排序方法。有如下几种值可选:

    • STRING: 按照字母顺序进行排序,为默认的排序方法
    • FILE_BASENAME:如果是一系列路径名,会使用basename 进行排序
    • NATURAL:使用自然数顺序排序
  • CASE:指明是否大小写敏感。有如下几种值可选:

    • SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
    • INSENSITIVE:按照大小写不敏感方式进行排序
  • ORDER:指明排序的顺序。有如下几种值可选:

    • ASCENDING: 按照升序排列,为默认值
    • DESCENDING:按照降序排列

宏定义

在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,如下所示:

c++ 复制代码
#include <stdio.h>
#define NUMBER  3

int main()
{
    int a = 10;
#ifdef DEBUG
    printf("我是一个程序猿, 我不会爬树...\n");
#endif
    for(int i=0; i<NUMBER; ++i)
    {
        printf("hello, GCC!!!\n");
    }
    return 0;
}

在程序的第七行对 DEBUG 宏进行了判断,如果该宏被定义了,那么第八行就会进行日志输出,如果没有定义这个宏,第八行就相当于被注释掉了,因此最终无法看到日志输入出(上述代码中并没有定义这个宏)。

为了让测试更灵活,我们可以不在代码中定义这个宏,而是在测试的时候去把它定义出来,其中一种方式就是在 gcc/g++ 命令中去指定,如下:

cmake 复制代码
add_definitions(-D宏名称)

针对于上面的源文件编写一个 CMakeLists.txt,内容如下:

cmake 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST)
# 自定义 DEBUG 宏
add_definitions(-DDEBUG)
add_executable(app ./test.c)

通过这种方式,上述代码中的第八行日志就能够被输出了。

指定编译模式

cmake编译类型通常默认为debug,但是在编译软件时,一般都需要使用release版本的,debug太慢了。设置为release版本可以在cmake文件里进行,也可以在运行cmake命令时使用参数进行指定。

在CMakeLists.txt直接设置

Debug模式

cmake 复制代码
SET(CMAKE_BUILD_TYPE "Debug")

Release模式

cmake 复制代码
SET(CMAKE_BUILD_TYPE "Release")

使用cmake命令时设置

Debug模式

bash 复制代码
mkdir bin
cd bin
cmake -DCMAKE_BUILD_TYPE=Debug ..
make

Release模式

bash 复制代码
mkdir bin
cd bin
cmake -DCMAKE_BUILD_TYPE=Release ..
make

其他

如果CMAKE_BUILD_TYPE的值为"Debug"就采用debug模式编译;如果CMAKE_BUILD_TYPE的值为"Release"就采用release模式编译;如果CMAKE_BUILD_TYPE的值为空,默认采用release模式

在最顶层的CMakeLists.txt中加入下面的代码

cmake 复制代码
IF(NOT CMAKE_BUILD_TYPE)
  SET(CMAKE_BUILD_TYPE Release)
ENDIF()
MESSAGE("Build type: " ${CMAKE_BUILD_TYPE})

在编译时输出如下图所示

C/C++相关设置

cmake 复制代码
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O3 -fPIC -ggdb -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function -Wno-builtin-macro-redefined -Wno-deprecated-declarations")

CMAKE_CXX_FLAGS

这种大写的变量,一般都是CMake预定义的内建变量,且他们是全局的。该变量可用于设置编译选项。直接使用set修改其值即可。

除了修改该变量的值之外,还可以通过add_compile_options命令来设置编译选项。它们的区别是:

add_compile_options命令添加的编译选项是针对所有编译器的(包括c和c++编译器)

CMAKE_C_FLAGSCMAKE_CXX_FLAGS变量则是分别只针对c和c++编译器

$ENV{}

$ENV{}用于获取环境变量设置环境变量的方法是:

cmake 复制代码
set(ENV{变量名} 值)
CXXFLAGS

CXXFLAGS是C++编译器的编译选项。

-rdynamic

-rdynamic编译选项通知链接器将所有符号添加到动态符号表中。(目的是能够通过使用 dlopen 来实现向后跟踪)

gcc选项-g与-rdynamic的异同

-O3

-O3是一个优化选项,告诉编译器优化我们的代码。

gcc -O0 -O1 -O2 -O3 四级优化选项及每级分别做什么优化

-fPIC

-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码,即,产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

gcc编译参数-fPIC的一些问题

-ggdb

-ggdb选项使编译器生成gdb专用的更为丰富的调试信息。

gcc 的 -g 和 -ggdb 选项

-std=c++11

-std=c++11设置为使用C++11标准

-Wall

-Wall选项告诉编译器 编译后显示所有警告。

gcc中的-w -W和-Wall选项

-Wno-deprecated

不要警告使用已弃用的功能

-Werror

告诉编译器视所有警告为错误,出现任何警告立即放弃编译

-Wno-unused-function

关闭 函数被定义了但没有被使用 而产生的警告,即,不使用的函数不警告。

-Wno-builtin-macro-redefined

如果某些内置宏被重新定义,请不要警告。这抑制了警告的重新定义__TIMESTAMP__,TIME,DATE,FILE,和__BASE_FILE__。

-Wno-deprecated-declarations

关闭使用废弃API的警告。

混编c和c++

参考:使用cmake混编c和c++

cmake 复制代码
project(main CXX C) # 这里可以不写CXX和C,但不能只写一个,否则只会编译指定语言
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")  #设置c++的编译选项
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")  #设置c的编译选项
aux_source_directory(./src dir_src) #指定路径,并产生变量${dir_src}
add_executable(main ${dir_src}) #指定代码目录
相关推荐
耶啵奶膘12 分钟前
uniapp-是否删除
linux·前端·uni-app
奋斗的小花生38 分钟前
c++ 多态性
开发语言·c++
闲晨44 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
2401_850410831 小时前
文件系统和日志管理
linux·运维·服务器
XMYX-02 小时前
使用 SSH 蜜罐提升安全性和记录攻击活动
linux·ssh
UestcXiye2 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
霁月风3 小时前
设计模式——适配器模式
c++·适配器模式
jrrz08284 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
二十雨辰4 小时前
[linux]docker基础
linux·运维·docker
咖啡里的茶i4 小时前
Vehicle友元Date多态Sedan和Truck
c++