CMake从安装到精通

news2024/11/26 23:23:47


目录

引言

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.coloradmin.cn/o/1832419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【图像分割】DSNet: A Novel Way to Use Atrous Convolutions in Semantic Segmentation

DSNet: A Novel Way to Use Atrous Convolutions in Semantic Segmentation 论文链接&#xff1a;http://arxiv.org/abs/2406.03702 代码链接&#xff1a;https://github.com/takaniwa/DSNet 一、摘要 重新审视了现代卷积神经网络&#xff08;CNNs&#xff09;中的atrous卷积…

计算机组成原理(四)Cache存储器

文章目录 Cache存储器的基本原理cache命中率、平均访问时间、效率地址映射全相联映射直接映射组相联映射 查找算法cache 存储器替换策略cache 存储器-写操作策略习题 Cache存储器的基本原理 Cache是一种高速缓冲寄存器&#xff0c;是为了解决CPU和主存之间速度不匹配而采用的一…

检索增强生成(RAG)的挑战与优化措施

如何理解检索增强生成&#xff08;RAG&#xff09; 简单来说&#xff0c;RAG就是让LLM通过外部知识源获取额外信息&#xff0c;从而生成更准确、更符合上下文的答案&#xff0c;并减少错误信息&#xff08;或称为“幻觉”&#xff09;的产生。 我们都知道&#xff0c;最先进的…

计数排序(Counting Sort)

计数排序&#xff08;Counting Sort&#xff09; 计数排序是一个非基于比较的排序算法&#xff0c;该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时&#xff0c;快于任何比较排序算法。排序思路: 1.找出待排序数组最大值2.定义一个索引最大…

Python学习打卡:day08

day8 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day858、数据容器(序列)的切片序列的常用操作——切片 59、序列的切片课后练习60、集合的定义和操作集合的定义集合的操作添加新元素移除元素从集合…

NATAPP-内网穿透工具----下载与配置

NATAPP-内网穿透工具 基于ngrok的国内高速内网穿透服务&#xff0c;natapp提供了一种便利的方式&#xff0c;使得开发和测试过程更加高效&#xff0c;尤其是在需要进行远程调试或展示时。无论是进行web开发、微信和支付宝的本地开发调试&#xff0c;还是简单地从外部网络访问家…

如何根据使用场景选购3D扫描仪?

三维扫描建模是指通过专业的三维扫描仪对产品进行三维数据的采集&#xff0c;快速获取物体精确的3D数据&#xff0c;实现1:1复刻原物体&#xff0c;扫描后所得的数字化3D模型以obj、fbx、glb、gltf等格式保存。 积木易搭自主研发多款三维扫描设备&#xff0c;拥有多项国家专利&…

初学者必看的web前端开发学习路线,干货满满!

初学者必看的web前端开发学习路线,干货满满&#xff01; 随着互联网的深入发展,前端工程师这个岗位在市场上的需求&#xff0c;薪资也是很可观的。前端很火&#xff0c;想自学前端的人也很多。包括一些学生、上班族、以前的UI&#xff0c;java&#xff0c;或完全零基础&#xf…

Nginx 高级应用

目录 一.使用alias实现虚拟目录 二. 通过stub_status模块监控nginx的工作状态 三. 使用limit_rate限制客户端传输数据的速度 四. nginx虚拟主机配置 1.基于端口的虚拟主机 2. 基于IP的虚拟主机 3. 基于域名的虚拟主机 nginx配置文件&#xff1a; /…

3阶段提交协议(3pc)

3阶段提交协议&#xff08;3pc&#xff09; 1 简介 三阶段提交协议是一个强一致、中心化的原子提交协议。解决了分布式事务、副本容错等分布式问题。其核心思想是将2PC的二阶段提交协议的“准备阶段”一分为二&#xff0c;形成了由CanCommit、PreCommit、DoCommit三个阶段组成…

LWIP移植

目录 前言一、以太网协议简介1.1 TCP/IP协议简介1.2 STM32的ETH外设1.2.1 MAC子层1.2.2 SMI站管理接口1.2.3 MII和RMII接口 1.3 外部PHY芯片LAN87201.3.1 LAN8720 中断管理1.3.2 PHY 地址设置1.3.3 nINT/REFCLKO 配置1.3.4 LAN8720 内部寄存器 1.4 LWIP 简介 二、带操作系统的移…

【热】大数据信用报告查询平台哪个比较好?这个平台值得一试!

在当今数字化时代&#xff0c;大数据技术的发展为个人和企业提供了更便捷、精准的信用报告查询服务。选择一个优秀的大数据信用报告查询平台至关重要&#xff0c;它直接影响到您获取信用信息的准确性和全面性。 首先&#xff0c;选择大数据信用报告查询平台时&#xff0c;您可以…

Flutter图像编辑器应用:创造生动美丽的照片体验

介绍 引言 想象一下&#xff0c;在一个阳光明媚的下午&#xff0c;与家人或朋友漫步在风景如画的街道上。拿出手机&#xff0c;迫不及待地捕捉这一刻的美好&#xff0c;按下快门&#xff0c;留下了一张充满回忆的照片。 然而&#xff0c;回到家后发现照片的亮度有些偏暗&…

大模型应用开发技术:Multi-Agent框架流程、源码及案例实战(二)

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

c函数/2024/6/17

1.递归计算0--n的和 #include <stdio.h> int sum(int n);//递归求和函数 int main(int argc, const char *argv[]) {//(2)递归计算0--n的和int n0;printf("请输入n的值为:");scanf("%d",&n);printf("0--n的和为:%d",sum(n));return 0…

CTFshow之RCE代码命令远程执行第41关到第52关详细讲解。可私信!

与其抱怨自己&#xff0c;不如埋怨他人。 --莫迪大仙 引言&#xff1a;今天来到ctf赛题RCE专题第二天&#xff08;今天肯定写不完了啊啊啊啊啊&#xff09;&#xff0c;不过继续加油&#xff01; 一、实验准备 1、ctf网址 2、firefox浏览器、hackbar插件 二、实验过程 &a…

大数据分析-二手车用户数据可视化分析

项目背景 在当今的大数据时代&#xff0c;数据可视化扮演着至关重要的角色。随着信息的爆炸式增长&#xff0c;我们面临着前所未有的数据挑战。这些数据可能来自社交媒体、商业交易、科学研究、医疗记录等各个领域&#xff0c;它们庞大而复杂&#xff0c;难以通过传统的数据处…

Day 25:1807. 替换字符串中的括号内容

Leetcode 1807. 替换字符串中的括号内容 给你一个字符串 s &#xff0c;它包含一些括号对&#xff0c;每个括号中包含一个 非空 的键。 比方说&#xff0c;字符串 “(name)is(age)yearsold” 中&#xff0c;有 两个 括号对&#xff0c;分别包含键 “name” 和 “age” 。 你知道…

Linux:生产消费模型 读者写者模型

Linux&#xff1a;生产消费模型 & 读者写者模型 生产消费模型阻塞队列基本结构构造与析构投放任务获取任务总代码 POSIX 信号量基本概念接口 环形队列基本结构构造与析构投放任务获取任务总代码 读者写者模型读写锁 生产消费模型 生产消费模型是一种用于处理多线程之间任务…

编译器优化入门(基于ESP32)

主要参考资料&#xff1a; kimi: https://kimi.moonshot.cn/ ESP-IDF 支持多种编译器&#xff0c;但默认情况下&#xff0c;它使用的是乐鑫官方提供的 Xtensa 编译器&#xff0c;这是一个针对 ESP32 芯片架构&#xff08;Tensilica Xtensa LX6 微处理器&#xff09;优化的交叉编…