CMake从安装到精通



目录

引言

1. CMake的安装

2. CMake的原理 

3. CMake入门

3.1 CMakeLists.txt与注释

3.2 版本指定与工程描述

3.3 生成可执行程序

3.4 定义变量与指定输出路径

3.5 指定C++标准

3.6 搜索文件 

3.7 包含头文件 

4. CMake进阶

4.1 生成动静态库

4.2 链接动静态库

4.3 日志

4.4 变量操作

 4.5 定义宏

5. CMake精通 

5.1 CMake的嵌套

5.2 条件判断

5.3 循环

结语


引言

CMake是一个跨平台的项目构筑工具。当项目规模庞大,依赖关系错综复杂时,编写 makefile 的工作量较大,解决依赖关系时也容易出错。同时makefileakefile 非常依赖于当前的编译平台,无法跨平台使用。而CMake以其出色的灵活性和强大的功能,成为了解决问题的理想选择。

本文笔者将详细介绍CMake的用法,帮助你高效地管理项目构建过程。

1. CMake的安装

在这里我们主要介绍Linux与Windows下的安装。

Linux下的安装

在Centos上,我们可以使用以下命令:

sudo yum install cmake

在Ubuntu或Debian上,我们可以使用以下命令:

sudo apt install cmake

安装完成后我们可以在终端下输入:

cmake -version

如果安装成功会显示cmake的版本。

  Windows下的安装

打开浏览器,访问CMake的官方网站下载页面:CMake Download。

下滑选择你要安装的版本,然后点击链接。

这里推荐选择第一个,注意如果选择的是zip格式需要手动解压并设置环境变量。

 下载后按指引安装即可。 打开命令窗口,输入cmake --version验证CMake 是否已正确安装

2. CMake的原理 

CMake并不直接编译源代码,它根据开发者在CMakeLists.txt文件指定的编译流程,生成适用于不同平台和编译器的本地化构建文件。

在Linux上是makefile文件,在Windows是Visual Studio解决方案文件(.sln)和项目文件(.vcxproj或.vcxproj.filters)。生成的这些文件也不编译源代码,他们用来描述工程的组织架构,帮助编译器编译。流程如下图:

简而言之,CMake之所以可以跨平台,是因为其在不同平台可以生成相应的构筑文件。 

同时它还能够检测系统环境并设置相应的编译器标志和库路径,进一步简化跨平台开发。支持条件逻辑,允许开发者根据不同的平台或编译器选项定制构建过程。

3. CMake入门

演示文件介绍

演示目录下共有六个文件:  head.h   add.cpp   div.cpp   sub.cpp   mul.cpp   main.cpp

add.cpp   div.cpp   sub.cpp   mul.cpp分别定义了加减乘除函数。

//add.cpp

#include "head.h"
int myadd(int x,int y)
{
    return x+y;
}
//sub.cpp

#include "head.h"
int mysub(int x,int y)
{
    return x-y;
}
//mul.cpp

#include "head.h"
int mymul(int x,int y)
{
    return x*y;
}
//div.cpp

#include "head.h"
int mydiv(int x,int y)
{
    return x/y;
}

   head.h声明了这些函数 。

#pragma once
int myadd(int x,int y);
int mydiv(int x,int y);
int mymul(int x,int y);
int mysub(int x,int y);

  main.cpp调用了这些函数。

#include<iostream>
#include"head.h"

int main()
{
    int x=6,y=3;
    std::cout << x << "+" << "y" << "=" << myadd(x,y) << std::endl;
    std::cout << x << "-" << "y" << "=" << mysub(x,y) << std::endl;
    std::cout << x << "*" << "y" << "=" << mymul(x,y) << std::endl;
    std::cout << x << "/" << "y" << "=" << mydiv(x,y) << std::endl;
    return 0;
}

当使用gcc编译文件我们可以使用以下命令:

g++ -std=c++11 -o program *.cpp

可以看到源文件成功编译,代码成功运行。

 下面我们演示如何使用CMake编译。

3.1 CMakeLists.txt与注释

首先我们需要创建一个CMakeLists.txt注意文件名严格区分大小写。创建成功后我们在文件中编写命令。这是CMake的基石。

行注释

在CMake中我们使用 # 进行行注释。效果类似C/C++中的 // 。

#这是一行注释

段注释

当我们要进行段注释时我们使用 #[[ ]]  ,效果类似C/C++中的 /* */ 。

#[[这是一段注释
这是一段注释
这是一段注释
这是一段注释]]

3.2 版本指定与工程描述

版本指定

在CMake的版本更新中会更新新的命令,这些命令在低版本并不兼容,所以需要通过cmake_minimum_required 指定需要的最低版本。这并不是必须的,但如果不加可能会有警告。

示例:

#语法
cmake_minimum_required(VERSION [版本号])
#示例
cmake_minimum_required(VERSION 3.0)

工程描述

我们可以使用 project 定义工程名称,工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。

  • 定义项目名称project(<PROJECT_NAME>)  这是最基本的用法,只需要指定项目名称。
  • 版本信息VERSION <major>[.<minor>[.<patch>[.<tweak>]]]  可以指定项目的版本号。
  • 项目描述DESCRIPTION "<description>"  可以为项目添加描述。
  • Web主页地址HOMEPAGE_URL "<url>"   可以指定项目的主页URL。
  • 支持的语言LANGUAGES <lang> [<lang>...]  可以指定项目支持的编程语言。如果不指定,默认情况下CMake支持多种语言,如C和C++。
  • 忽略可选参数:如果不需要设置版本、描述、主页或特定语言,可以忽略这些参数,只指定项目名称 

示例:

#语法
project(<PROJECT-NAME>
       [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
       [DESCRIPTION <project-description-string>]
       [HOMEPAGE_URL <url-string>]
       [LANGUAGES <language-name>...])
#示例
#定义项目名称,版本,描述,主页URL,以及支持的语言
project(
    MyProject
    VERSION 1.0.0
    DESCRIPTION "这是一个示例项目"
    HOMEPAGE_URL "http://www.example.com"
    LANGUAGES CXX
)

现在我们在CMakeLists.txt中指定版本信息与工程描述。

3.3 生成可执行程序

我们已经指定了版本信息与工程描述。现在我们需要使用 add_executable 定义工程生成的可执行程序。

//语法
add_executable(可执行程序名 源文件名称)
//示例
add_executable(program main.cpp add.cpp sub.cpp mul.cpp div.cpp)

现在我们继续完善CMakeLists.txt

一个基本的 CMakeLists.txt 完成了。

我们使用cmake命令构筑项目。

#语法
cmake CMakeLists.txt文件所在路径
#示例
cmake .

输入cmake . 命令 ,可以看到当前目录生成了许多文件。

对应的目录下生成了makefile文件,此时执行make命令便可到可执行文件。

可以看到编译成功,我们执行程序。

程序执行成功。在上面的过程中我们发现执行cmake后会生成一堆文件,使得目录十分杂乱。我们可以单独创建一个目录,在该目录下执行cmake .. 文件会创建在此目录,更加整洁。

 需要注意的是此时只能在 build 目录下执行,生产的可执行文件也在 build 目录。

3.4 定义变量与指定输出路径

在上面的过程中,我们使用了五个源文件。如果这些源文件需要反复使用,我们每次都需要将他们的名称写出来,这是非常低效的。cmake为我们提供了 set 指令来定义变量与设置宏

#语法
set(VARIABLE_NAME value [CACHE_TYPE [CACHE_VARIABLE]])
  • VARIABLE_NAME:变量的名称。
  • value:为变量赋予的值。
  • CACHE_TYPE(可选):指定缓存变量的类型,如 FILEPATH、PATH、STRING、BOOL 等。
  • CACHE_VARIABLE(可选):如果指定,变量将被存储在 CMake 缓存中,而不是只限于当前的 CMakeLists.txt 文件。 

后两项我们暂时不做讨论。现在我们可以将要使用的源文件名存储在变量里

#定义一个变量SOURCE_FILE,存储源文件名
set(SOURCE_FILE main.cpp add.cpp sub.cpp div.cpp mul.cpp)

如果要取变量中的值语法格式为:

${变量名}

 现在我们修改CMakeLists.txt,使用变量存储文件名。

编译并运行程序。

可以看到程序成功运行。

上面我们提到可以单独创建一个目录,在该目录下执行cmake .. 这样的操作使目录更加整洁,但这样可执行文件就会生成在build目录下,能不能指定可执行文件输出路径呢?CMake为我们提供了一个宏 EXECUTABLE_OUTPUT_PATH 我们可以通过设置这个宏指定输出路径。这里的输出路径支持相对路径与绝对路径。我们可以使用 set 命令设置宏。

#定义一个变量存储路径,输出路径为上一级的bin目录
set(OUTPATH  ../bin)
#设置宏
set(EXECUTABLE_OUTPUT_PATH ${OUTPATH})

注意:如果输出路径中的子目录不存在,会自动生成。

现在我们修改CMakeLists.txt,指定输出路径。

 可以看到成功创建了目录 bin 并生成了可执行程序。 

3.5 指定C++标准

在这里我们再额外介绍一个宏 CMAKE_CXX_STANDARD 。这个宏用来指定C++标准。在CMake中想要指定C++标准有两种方式:通过set命令指定,在执行cmake指令时指定。

我们在main.cpp中添加一行代码

auto x=6;

auto关键字在 C++11 引入,下面我们分别演示用通过set命令指定C++11在执行cmake指令时指定。

通过set命令指定C++11

我们需要通过set命令设置 CMAKE_CXX_STANDARD 的值,示例如下

set(CMAKE_CXX_STANDARD 11)

同样修改CMakeLists.txt后运行。

文件成功编译。

 执行cmake指令时指定

 我们注释掉CMakeLists.txt中指定CMAKE_CXX_STANDARD的命令。在执行cmake指令时设置CMAKE_CXX_STANDARD的值。注意 CMAKE_CXX_STANDARD 需要的最低版本为3.1。这里并没有更改最低版本

我们使用cmake命令

cmake .. -DCMAKE_CXX_STANDARD=11

可以看到同样编译成功。

3.6 搜索文件 

在我们的示例文件中只有五个源文件,如果有大量源文件,那么需要一个一个罗列出来十分繁琐。cmake中同样提供了搜索文件的命令 aux_source_directory  file 命令。

 aux_source_directory

aux_source_directory 命令可以查找某个路径下的所有源文件,语法:

aux_source_directory(<directory> <variable>)
  • <directory>: 要搜索源文件的目录的路径。这可以是相对路径或绝对路径。
  • <variable>: 用于存储找到的源文件列表的变量名。

 示例使用:

#搜索上一级目录的源文件
aux_source_directory(.. SOURCE_FILE)

现在我们修改CMakeLists.txt并执行cmake命令。

CMAKE_CURRENT_SOURCE_DIR 是 CMake 中的一个预定义变量,它指向当前正在处理的 CMakeLists.txt 文件所在的目录。注意:如果使用相对路径,相对路径是相对于CMakeLists.txt 文件所在的目录,而非执行cmake命令的目录。

可以看到成功编译。 

file

file 命令用于对文件和目录进行操作,包括检查文件属性、读取和写入文件内容、复制文件、删除文件等。在这里我们只介绍一种用法搜索文件。语法:

file(<GLOB/GLOB_RECURSE> <VARIABLE> <PATH>)
  • <GLOB/GLOB_RECURSE>选择非递归搜索(GLOB)还是递归搜索(GLOB_RECURSE),递归搜索会搜索路径下的所有目录。
  • <VARIABLE>存储搜索结果的变量。
  • <PATH>搜索的路径与搜索的文件名。

file使用相对路径时同样相对于CMakeLists.txt 文件所在的目录,而非执行cmake命令的目录。 示例: 

#搜索CMakeLists.txt路径下所有源文件并存储在SOURCE_FILE
file(GLOB SOURCE_FILE ./*.cpp)

修改CMakeLists.txt,同样可以成功编译

3.7 包含头文件 

现在我们将工程结构调整为下面的结构(文件搜索路径同步调整):

 我们执行cmake命令。

发现执行失败,这是为什么?我们的源文件都包含了head.h头文件。 

#include"head.h"

当我们使用 " " 方式包含头文件时默认从当前源文件所在路径搜索。如果当前目录下没有找到,编译器会搜索项目中指定的其他包含目录(通过编译器的-I选项或在IDE中设置的包含路径来指定的)。如果以上目录都没有找到,编译器会搜索系统的标准库包含目录。

在我们调整了工程结构后头文件与源文件不在同一目录,我们又没有指定头文件搜素路径。所以找不到头文件。我们可以使用include_directories 指定头文件搜索路径。

#语法
include_directories([headpath])
#示例
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

现在我们在 CMakeLists.txt 加入头文件搜索路径

再次执行cmakemake命令。 

程序编译成功。 

4. CMake进阶

现在我们来学习使用cmake制作动态库与静态库,对动静态库不熟悉的读者可以阅读我的往期博客——动态库与静态库。

4.1 生成动静态库

生成静态库

生成静态库需要用到命令 add_library 语法:

#语法
add_library([库名称] SHARED/STATIC [源文件1] [源文件2] ...) 
#示例生成一个名为libmymath.a的静态库
add_library(mymath STATIC add.cpp sub.cpp mul.cpp div.cpp) 

在Linux中,静态库名字分为三部分:lib+库名+.a,命令需要指出的是中间部分,另外两部分在生成库文件时会自动补全。命令的第二个选项代表生成的是静态库(STATIC)还是动态库(SHARED)。

现在我们调整一下工程结构(CMakeLists.txt 同步调整 )。

在 CMakeLists.txt 中我们删除add_executable,添加add_library生成静态库。

我们执行cmake命令

可以看到build 目录下成功生成了静态库文件 libmymath.a 。

生成动态库

生成静态库需要用到命令 add_library 只需将第二个参数由STATIC改为SHARED。我们修改CMakeLists.txt。与静态库相同命令只需要指出名字中间部分

执行cmake命令并编译。

可以看到build 目录下成功生成了动态库文件 libmymath.so 。

指定库文件的输出路径

指定库文件的输出路径有两种方法:设置 EXECUTABLE_OUTPUT_PATH 指定输出路径,设置 LIBRARY_OUTPUT_PATH 指定输出路径。

使用 EXECUTABLE_OUTPUT_PATH 指定输出路径只对动态库有效,因为Linux下生成的动态库默认是有执行权限的,而静态库没有。

我们在这只演示通过 LIBRARY_OUTPUT_PATH 指定输出路径。

我们执行cmake并编译。

可以看到库文件被成功生成到指定的目录下。 

4.2 链接动静态库

cmake 链接库的命令为target_link_libraries target_link_libraries 可以链接动态库与静态库。

target_link_libraries(<target>  <PRIVATE|PUBLIC|INTERFACE> <item>...)
  • <target>:要链接库的目标名称,可以是可执行文件或库。
  • <PRIVATE|PUBLIC|INTERFACE>:指定链接库的可见性:PRIVATE:链接库仅对当前目标有效,不会传递给依赖该目标的其他目标。 PUBLIC:链接库对当前目标及其依赖者都有效,链接属性会传递给依赖该目标的其他目标。 INTERFACE:指定仅对依赖该目标的其他目标可见的接口链接库,不包括其实现细节。
  • <item>...:一个或多个库的名称或目标名称,可以是库文件的路径、目标名称,或者是使用 find_package 或 find_library 找到的库名称。 

关于可见性问题可能不太好理解,我们举例说明。现在有以下CMake命令

# 库A依赖B和C
target_link_libraries(A PUBLIC B PUBLIC C)

# 动态库D链接库A
target_link_libraries(D PUBLIC A)

在这个例子中:

  • A链接了B和C,并且使用了PUBLIC关键字,所以任何链接到A的库(包括D)也会链接B和C。
  • 由于D链接了A,并且同样使用了PUBLIC关键字,D的任何依赖者也将链接A、B和C。

如果将PUBLIC更改为PRIVATE或INTERFACE,链接行为将相应改变:

  • 使用PRIVATE,D将链接A,但D的依赖者不会链接A、B或C。
  • 使用INTERFACE,D将不会链接A的实际实现,但D的依赖者将能够使用A定义的接口。

如果target_link_libraries target_link_libraries  链接的是第三方库,需要用 link_directories 指定库所在的路径。

link_directories(<libpath>)

现在我们调整工程结构如下。

我们链接lib中的 libmymath.so 。 CMakeLists.txt 中添加以下命令:

link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)
target_link_libraries(program  libmymath.so)

我们执行cmake命令并编译

可以看到程序成功运行。需要注意的是 target_link_libraries target_link_libraries 命令需要写在生成目标文件之后。

4.3 日志

在CMake中,我们可以使用message命令记录日志或输出信息到控制台。这个命令允许输出不同级别的信息,包括普通消息、警告和错误。

message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message")
  • STATUS:显示状态消息,通常不是很重要。
  • WARNING:显示警告消息,编译过程会继续执行。
  • AUTHOR_WARNING:显示作者警告消息,用于开发过程中,编译过程会继续执行。
  • FATAL_ERROR:显示错误消息,终止所有处理过程。
  • SEND_ERROR:显示错误消息,但继续执行,会跳过生成步骤。

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

我们简单测试一下。

执行cmake命令

可以看到信息正常输出,在输出错误信息后编译终止。

4.4 变量操作

在在CMake中,所有的变量都为字符串类型,变量操作实际上也是字符串操作。CMake提供了多种命令来操作这些字符串变量。

拼结变量

拼接字符串可以通过 list 命令与 set 命令进行。

#使用set进行拼接
set(变量名1 ${变量名1} ${变量名2} ...)
#示例使用
set(variables1 ${variables1} ${variables2})

我们简单演示一下,并使用 message 输出。

 

可以看到成功输出。

使用 list 命令拼接变量

#语法
list(APPEND 变量名1 ${变量名2} ...)
#示例使用
list(APPEND variables1 ${variables2})

我们简单演示一下  

 移除字符串

有时候我们需要从变量中移除字串这时候我们可以也使用 list 

#语法
list(REMOVE_ITEM 变量名 要移除的子串)
#示例
list(REMOVE_ITEM  VAR "Hello ")

我们简单演示一下

list 命令还有许多用法我们这里不再一 一演示。

  • 创建和初始化列表

    set(MY_LIST item1 item2 item3)

  • 追加元素list(APPEND ...)):

    list(APPEND MY_LIST "new_item1" "new_item2")

  • 插入元素list(INSERT ...)):

    list(INSERT MY_LIST 1 "item1.5") # 在位置1插入元素

  • 移除元素list(REMOVE_ITEM ...)):

    list(REMOVE_ITEM MY_LIST "item2")

  • 移除指定索引的元素list(REMOVE_AT ...)):

    list(REMOVE_AT MY_LIST 1) # 移除索引为1的元素

  • 获取列表长度list(LENGTH ...)):

    list(LENGTH MY_LIST LENGTH_OF_LIST)

  • 获取特定索引的元素list(GET ...)):

    list(GET MY_LIST 0 FIRST_ITEM)

  • 设置特定索引的元素

    list(SET MY_LIST 1 "new_item2") # 设置索引为1的元素

  • 连接列表元素为字符串list(JOIN ...)):

    list(JOIN MY_LIST ", " JOINED_STRING)

  • 分割字符串为列表string(REPLACE ...)list(APPEND ...)结合使用):

    string(REPLACE "," ";" MY_LIST "${SOME_STRING}")

  • 查找元素list(FIND ...)):

    list(FIND MY_LIST "item1" INDEX)

  • 反转列表list(REVERSE ...)):

    list(REVERSE MY_LIST)

  • 排序列表list(SORT ...)):

    list(SORT MY_LIST) # 默认升序排序

  • 复制列表list(COPY ...)):

    list(COPY MY_LIST COPY_OF_MY_LIST)

  • 清除列表list(CLEAR ...)):

    list(CLEAR MY_LIST)

 4.5 定义宏

在CMake中,宏主要分为两种:CMake脚本中的宏和C++源代码中通过CMake定义的条件编译宏。

条件编译宏

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

#include<iostream>

int main()
{
#ifdef DEBUG
    std::cout<< "DEBUG" << std::endl;
#endif
#ifndef DEBUG
    std::cout<< "NDEBUG" << std::endl;
#endif
    return 0;
}

我们可以在CMake脚本中定义条件编译宏 。命令为 add_definitions

#定义宏
add_definitions(-D宏名称)
#定义宏并赋值
add_definitions(-DDEBUG=1)

我们修改 CMakeLists.txt 并简单测试

可以看到成功定义了DEBUG。

CMake中的宏

CMake中的宏是一系列可以被多次调用的CMake命令,可以接收参数,类似于函数。用于封装重复使用的构建逻辑。我们可以使用 macro 和 endmacro 命令定义宏。宏在定义它们的CMake文件中全局可见。

macro(MyMacro arg1 arg2) #定义宏
  message(STATUS "Arg1: ${arg1}")
  message(STATUS "Arg2: ${arg2}")
endmacro()               #定义宏结束
# 调用宏
MyMacro(value1 value2)

简单演示一下

5. CMake精通 

到这里相信你已经掌握了CMake的基础用法,下面让我们进一步学习CMake的使用。

5.1 CMake的嵌套

当我们的项目很大时,项目中会有很多的源码目录,如果只使用一个CMakeLists.txt,会比较复杂,我们可以给每个源码目录都添加一个CMakeLists.txt文件,这样每个文件都不会太复杂,而且更灵活,更容易维护。

在这个工程中我们有五个目录,我们在 build 目录下执行CMake命令。在src下我们生成一个动态库,在test目录下我们链接动态库生成一个可执行文件。

嵌套的CMake是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次是子节点。我们需要使用 add_subdirectory() 命令在结点间建立父子关系。

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
  • source_dir:要添加的子目录的路径,相对于当前 CMakeLists.txt 文件的路径。
  • binary_dir(可选):构建输出的目录,如果未指定,CMake 会使用 source_dir 作为构建目录。
  • EXCLUDE_FROM_ALL(可选):如果指定,该子目录的构建目标不会包含在 all 目标中,即默认情况下不会在调用 make 时构建。

后两项我们通常用不到可以忽略,在建立关系后,父节点的变量可以被子节点继承,执行cmake命令时,也会一起处理。

下面我们来编写根目录的 CMakeLists.txt ,这里只需要定义一下工程名称,最低版本,建立父子关系,定义一些变量即可。

下面我们编写 src 下的 CMakeLists.txt,在这部分我们需要生成一个动态库并输出到lib目录下。

在  test 下的 CMakeLists.txt ,我们需要链接动态库生成可执行文件。

现在我们完成了准备工作,我们在build目录下执行cmake命令。

可以看到程序成功运行。

5.2 条件判断

cmake下的条件判断与C语言基本类似。不同的是cmake需要使用endif() 结束 if 语句块

if(条件1)
  # 条件为真时执行的命令
elseif(条件2)
  # 条件为真时执行的命令
else()
  # 没有条件为真时执行的命令
endif()

条件主要包括以下种类:

  • 变量:检查变量是否存在或其值是否符合特定条件。
  • 平台:根据操作系统、编译器等平台特性进行判断。
  • 文件和目录:检查文件或目录是否存在。
  • 逻辑运算:使用逻辑运算符来组合多个条件。

变量 

变量的判断有以下关键字:DEFINED、EXISTS、IS_DIRECTORY

DEFINED:DEFINED 用于检查变量是否已经被定义。它不检查变量的值,只检查变量是否存在。 

if(DEFINED MY_VARIABLE)
  message(STATUS "MY_VARIABLE is defined.")
endif()

EXISTS: EXISTS 用于检查文件或目录是否存在。接受一个路径作为参数,并返回一个布尔值。 

if(EXISTS "${CMAKE_SOURCE_DIR}/somefile.txt")
  message(STATUS "The file somefile.txt exists.")
endif()

 IS_DIRECTORY: IS_DIRECTORY 用于检查给定的路径是否是一个目录。如果路径是一个存在的目录,返回布尔值。

if(IS_DIRECTORY "${CMAKE_SOURCE_DIR}/somedir")
  message(STATUS "The path somedir is a directory.")
endif()

逻辑运算 

 CMake 支持AND, OR, NOT 逻辑运算符来进行更复杂的条件判断。

  • AND(同C语言 && ):逻辑与。两个条件都必须为真,整个表达式才为真。
  • OR(同C语言 || ):逻辑或。两个条件中至少有一个为真,整个表达式就为真。
  • NOT(同C语言 ! ):逻辑非。反转条件的真假。

平台 

平台判断包括检查操作系统、编译器、架构,我们这里主要介绍操作系统判断。 

CMake 提供了一些预定义的变量来标识操作系统类型,例如 WIN32, UNIX, 和 APPLE

if(WIN32)
  message(STATUS "Windows") //Windows
elseif(UNIX)
  message(STATUS "Unix")    //Linux
elseif(APPLE)
  message(STATUS "macOS")   //苹果
endif()

比较

 条件判断必不可少涉及到比较。我们这里介绍数值比较与字符串比较。

数值比较:

  • LESS <: 检查左侧是否小于右侧。

  • GREATER >: 检查左侧是否大于右侧。

  • EQUAL ==: 检查两侧是否数值相等。

  • NOTEQUAL !=: 检查两侧是否数值不相等。

字符串比较:

  • STRLESS: 字符串是否字典序较小。
  • STRGREATER: 字符串是否字典序较大。
  • STREQUAL: 字符串是否相等。
  • NOT STREQUAL: 字符串是否不相等。
# 定义变量
set(a 10)
set(b 20)

# 数值比较
if(a LESS b)
    message("a<b")
endif()

if(a EQUAL 10)
    message("a=10")
endif()

# 字符串比较
if(a STREQUAL "10")
    message("a = '10'")
endif()

算术运算 

同样在循环与条件判断中算数运算必不可少,cmake为我们提供 math 命令用于执行算术运算。

加法

set(counter 1) math(EXPR counter "${counter} + 1") # counter 现在是 2

减法

set(counter 5) math(EXPR counter "${counter} - 2") # counter 现在是 3

乘法

set(counter 3) math(EXPR counter "${counter} * 2") # counter 现在是 6

除法

set(counter 20) math(EXPR counter "${counter} / 4") # counter 现在是 5

模运算

set(counter 7) math(EXPR counter "${counter} % 3") # counter 现在是 1

使用变量

set(a 10) 
set(b 3) 
math(EXPR result "${a} * ${b}") # result 是 30

5.3 循环

CMake中的循环分为两种,foreach 循环和 while 循环。 

foreach 循环

 foreach循环的基本语法如下:

foreach(<variable> IN <list>)
    # 命令
endforeach()
  • <variable>:这是循环变量,每次迭代都会赋予它列表中的一个元素。
  • <list>:这是要遍历的元素列表,可以是列表、数组或任何可迭代的对象。

 示例使用

 

while循环 

while循环比较简单,只需要指定出循环结束的条件。

while(<condition>)
    # 命令序列
endwhile()

这里的 <condition> 是一个布尔表达式,每次循环迭代开始时都会进行判断。只要条件为真,循环就会继续执行。一旦条件为假,循环就会终止。

示例使用

结语

到这里我们已经完成了CMake的学习。笔者能力有限,如有错漏之处,欢迎指正。同时很多命令语法只是讲了基础用法,如果有读者想要深入研究可以参考CMake的官方文档。制作不易,求点赞关注。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/717631.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux服务升级:Almalinux 升级 Anaconda虚拟环境

目录 一、实验 1.环境 2.Almalinux 部署 VS Code 3.Almalinux 升级 Anaconda虚拟环境 4.Anaconda 使用 5.VS Code 部署 Anaconda虚拟环境 6.Almalinux 使用命令窗口版 Jupyter 二、问题 1.VS Code离线如何实现汉化 2.Anaconda 安装报错 3.Anaconda创建虚拟空间失败 …

头歌资源库(9)丢失的数字

一、 问题描述 二、算法思想 输入n和nums数组。初始化一个大小为n1的数组counts&#xff0c;初始值都为0。遍历nums数组&#xff0c;将counts[nums[i]]的值加1。遍历counts数组&#xff0c;找到第一个值为0的索引&#xff0c;即为没有出现在数组中的那个数。输出结果。 三、…

视频太大了怎么压缩

随着科技的不断发展&#xff0c;视频已经成为了我们生活和工作中不可或缺的一部分。但是&#xff0c;当我们拍摄或下载的视频文件过大时&#xff0c;就会遇到很多麻烦。比如&#xff0c;不能发送给朋友、不能上传等。这时候&#xff0c;我们就需要对视频进行压缩&#xff0c;以…

ctfshow web七夕杯

web签到 执行命令没有回显&#xff0c;我们直接写文件就可以了 有字符长度限制 ls />a nl /*>a访问url/api/a下载文件 easy_calc <?phpif(check($code)){eval($result."$code".";");echo($result); }function check(&$code){$num1…

谷歌发布Infini-Transformer模型—无限注意力机制长度,超越极限

Transformer 是一种基于自注意力机制的深度学习模型&#xff0c;最初应用于自然语言处理领域&#xff0c;现已扩展到图像、音频等多个领域。与传统的循环神经网络 (RNN) 不同&#xff0c;Transformer 不依赖于顺序数据处理&#xff0c;能够并行计算&#xff0c;从而显著提高效率…

04通讯录管理系统——退出功能实现

功能描述&#xff1a;退出通讯录 思路&#xff1a;根据用户不同的选项&#xff0c;进入不同的功能&#xff0c;可以选择switch分支结构&#xff0c;将整个架构进行搭建 当用户选择0时&#xff0c;执行退出&#xff0c;选择其他时先不做操作&#xff0c;也不退出程序 代码&am…

2021 hnust 湖科大 数据结构课堂实验代码及报告

2021 hnust 湖科大 数据结构课堂实验代码及报告 目录 实验1 线性表 1 实验2 栈的应用 4 实验3 队列的应用 9 实验4 串的应用 16 实验5 二叉树遍历 19 实验6 哈夫曼编码 23 实验7 图的遍历 27 实验8 最小生成树 32 实验9 最短路径 35 实验10 折半查找 37 实验11 插入排序与交换…

vue3的基本使用方法

【 vue3实例 】 【 0 】对象、方法和属性 对象&#xff08;Object&#xff09;&#xff1a; 对象是编程中的一个数据结构&#xff0c;它可以包含多种数据类型&#xff0c;包括数字、字符串、布尔值、数组、其他对象等。对象通常由一系列属性和方法组成。在面向对象编程&…

黑马程序员2024最新SpringCloud微服务开发与实战 个人学习心得、踩坑、与bug记录Day3 全网最全

你好,我是Qiuner. 为帮助别人少走弯路和记录自己编程学习过程而写博客 这是我的 github https://github.com/Qiuner ⭐️ gitee https://gitee.com/Qiuner &#x1f339; 如果本篇文章帮到了你 不妨点个赞吧~ 我会很高兴的 &#x1f604; (^ ~ ^) 想看更多 那就点个关注吧 我会…

DoIP——step1:车辆连接

文章目录 前言一、连接要求DoIP边缘节点的接口要求/测试仪的接口要求DoIP边缘节点物理层要求DoIP边缘节点数据链路层要求测试设备数据链路层要求二、物理连接激活线三、线束要求前言 采用DoIP对车辆进行诊断时可选择有线或者无线的方式完成外部诊断设备和车辆的连接,接下来将…

文档项目:攻坚克难

鉴于交流离心机存在的缺点&#xff1a;转速相对偏差、稳定精度不够高&#xff1b;带负载能力受外界扰动后&#xff0c;波动较大&#xff1b;寿命短&#xff0c;研究所各相关部门成立组成技术攻关团队&#xff0c;齐心协力&#xff0c;攻坚克难&#xff0c;在摸索中突破创新&…

【实用工具】Skyworking

文章目录 一、Skywalking概述1.1 核心功能1.2 指标概述1.2.1 一些常用指标1.2.2 指标的使用方式 二、仪表盘2.1 APM2.1.1 全局维度的指标2.1.2 服务维度的指标2.1.3 实例维度的指标2.1.4 端点维度的指标 2.2 数据库2.3 其他 三、追踪3.1 界面划分3.2 请求链路界面分析3.3 一般流…

Unity 材质系统优化(mesh相同,图片不同,但是可以将所有的图片合成一张图集)

今天提供一个Unity材质优化的思路&#xff0c;流程是这样的&#xff0c;模型的mesh相同只是图片不同&#xff0c;我想着能不能将所有的图片合成一张图集呢&#xff0c;于是我就试着在Blender里面开搞了&#xff0c;所有的mesh相同的模型&#xff0c;共用一个材质&#xff08;图…

spring整合openAI大模型之Spring AI

文章目录 一、SpringAI简介1.什么是SpringAI2.SpringAI支持的大模型类型&#xff08;1&#xff09;聊天模型&#xff08;2&#xff09;文本到图像模型&#xff08;3&#xff09;转录&#xff08;音频到文本&#xff09;模型&#xff08;4&#xff09;嵌入模型&#xff08;5&…

广电行业没落了吗?生成式人工智能(AIGC)媒体应用标准联盟发布,超清化、移动化和智能化是发展趋势

这些年&#xff0c;随着智能手机、网络普及程度越来越高&#xff0c;传统广播电视的打开率下滑&#xff0c;广电行业甚至被纳入“特困”行业名单。 根据2022年5月31日&#xff0c;人社部等四部门发布的《关于扩大阶段性缓缴社会保险费政策实施范围等问题的通知》&#xff0c;其…

高频谐振功放

目录 基本电路原理 高频谐振功放vs.高频谐振小放 高频谐振功放vs.低频功放 功率与效率 计算 基本电路原理 由于加了VBB&#xff0c;基极反偏&#xff0c;拉低了输入信号的直流偏量&#xff08;如下图中-VBB&#xff09;,加上三极管截至电压VBE的存在&#xff0c;只有一部分…

Web后端开发的学习

REST风格 GET:查询用户POST:新增用户POT:修改用户DELETE:删除用户 前后端交互统一的响应结果 记录日志 SLf4j 注解&#xff1a; PathVariable&#xff1a;获取路径的参数ResponseBody :方法的返回值直接作为 HTTP 响应的正文返回,将响应的实体类转为json发送给前端Request…

WPF学习(4)--SCICHART学习

一、项目创建过程 1.下载SCICHART插件 2.选中第一个&#xff0c;确保引用中有我们要用的 二、示例代码 1.前端代码 <Window x:Class"SciChart.Examples.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"h…

LSS算法核心原理详细解读,一看就懂,不懂请打我!

目录 核心整体流程分步阐述backbone几何关系&#xff08;创建视锥&#xff09;和视锥投影到egoVoxel PoolingHead 总结 核心 将2D图像特征转换到BEV feature特征 该算法是BEV领域中的一大基石 整体流程 流程步骤 &#xff08;1&#xff09;利用backbone获得环视图像&#xf…

配置文件-基础配置,applicationproperties.yml

黑马程序员Spring Boot2 文章目录 1、属性配置2、配置文件分类3、yaml文件4、yaml数据读取4.1 读取单个数据4.2 读取全部属性数据4.3 读取引用类型属性数据 1、属性配置 SpringBoot默认配置文件application.properties&#xff0c;通过键值对配置对应属性修改配置 修改服务器端…