makefile从入门到实战 第二章 认识CMake(二)

参考教程:https://www.bilibili.com/video/BV14s4y1g7Zj/?spm_id_from=333.1387.favlist.content.click&vd_source=8f8a7bd7765d52551c498d7eaed8acd5

文章内容主要参考:https://subingwen.cn/cmake/CMake-primer/#2-4-%E5%8C%85%E5%90%AB%E5%A4%B4%E6%96%87%E4%BB%B6

二、CMake进阶

1、通过CMake制作库文件

(1)有时候编写源代码并不是为了编译生成可执行程序,而是生成一些静态库或动态库,提供给第三方使用。

(2)制作静态库:

①CMake提供了制作静态库的命令:

add_library(<库名称> STATIC <源文件1> [<源文件2> ...]) 
# STATIC表示生成的是静态库文件
# 实际生成的静态库文件,名为lib<库名称>,命令中“lib”是省略的
# 源文件可有多个,但至少需要有1个

②举例:

        项目的目录结构如下(Linux环境)

        那么相应地,CMakeLists.txt文件的内容如下,最终生成静态库文件libcalc.a

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})

(3)制作动态库:

①CMake提供了制作动态库的命令:

add_library(<库名称> SHARED <源文件1> [<源文件2> ...]) 
# SHARED表示生成的是动态库文件
# 实际生成的动态库文件,名为lib<库名称>,命令中“lib”是省略的
# 源文件可有多个,但至少需要有1个

②举例:

        项目的目录结构如下(Linux环境)

        那么相应地,CMakeLists.txt文件的内容如下,最终生成动态库文件libcalc.a

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc SHARED ${SRC_LIST})

(4)指定库文件的输出路径:

①在Linux环境下生成的动态库,默认有执行权限,所以可以按照可执行程序的方式去指定它的生成目录(通过set命令设置宏EXECUTABLE_OUTPUT_PATH),如果路径不存在则自动创建。

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})

②不管是动态库还是静态库,只要是生成库文件,那么都可以通过set命令设置宏LIBRARY_OUTPUT_PATH,以设置库文件的生成路径,如果路径不存在则自动创建。

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})

2、在程序中链接静态库

(1)在CMake中,链接静态库的命令如下:

link_libraries(<static lib> [<static lib>...])
# <static lib>:要链接的静态库名,可以是全名,也可以掐头去尾(去掉“lib”和后缀)
# 一条命令可链接若干个静态库,但至少链接一个

(2)如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库),可能出现静态库找不到的情况,此时可以使用命令link_directories,将静态库的路径也指定出来。

link_directories(<lib path>)

(3)举例:

        项目的目录结构如下(Linux环境)

        那么相应地,CMakeLists.txt文件的内容如下

cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})

3、在程序中链接动态库

(1)在程序编写过程中,除了在项目中引入静态库,大多时候也会使用一些标准的或者第三方提供的一些动态库。

(2)在CMake中,链接动态库的命令如下:

target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
# 指定目标<target>(可以是可执行文件或库文件)在编译时需要链接的库,必须是用add_executable命令或add_library命令创建的目标名
# <item>为要链接的库名
# 在CMake中指定链接动态库的时候,应将命令写在生成可执行文件之后

(3)动态库的访问权限:

①动态库的连接具有传递性,如果动态库A链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。

target_link_libraries(A B C)
target_link_libraries(D A)

②三个访问权限:

[1]PUBLIC:在PUBLIC后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。(适用情景:库A用了库B,其它链接库A的目标也需要库B)

[2]PRIVATE:在PRIVATE后面的库仅被Link到前面的target中,并且终结掉,第三方无法感知target调了什么库。(适用情景:可执行文件用了一个内部库,头文件不对外暴露)

[3]INTERFACE:在INTERFACE后面引入的库不会连接到前面的target中,只会导出符号。(适用情景:库只是转发了其它库的头文件,自身不链接)

③如果各个动态库之间没有依赖关系,无需做任何设置,三者没有区别,一般无需指定,使用默认的PUBLIC即可。

(4)链接系统动态库(如libpthread.a)比较简单,如下所示:

cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)

(5)如果是链接第三方库,需要通过命令link_directories指定出要链接的动态库的位置(与指定静态库位置使用的命令一致),如下所示:

cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库
target_link_libraries(app pthread calc)

        需要注意的是,命令link_directories只能解决编译链接阶段找不到库文件的问题,当程序运行时,如果库路径不在操作系统的检索范围内,操作系统还是会找不到库文件,对此有几种解决办法:

        ①将库安装到系统标准路径

        ②运行前设置“export LD_LIBRARY_PATH=项目绝对路径/lib”,扩充运行时的搜索路径(这种属于临时方案)

        ③在编译时用RPATH把库路径硬编码到可执行文件中

add_executable(app main.cpp)
# 给app单独设置RPATH
set_target_properties(app PROPERTIES
    BUILD_RPATH "${PROJECT_SOURCE_DIR}/lib"
    INSTALL_RPATH "${PROJECT_SOURCE_DIR}/lib"
)

(6)实际上,target_link_libraries和 link_libraries都可以用于链接动态库和静态库,但实际中前者更常用,后者只能设置全局链接库,这些库会链接到定义的所有目标上,影响所有的目标,缺乏针对具体目标的控制,容易导致意外的依赖关系,不适合复杂的项目结构。

4、CMake日志

(1)在CMake中可以用命令显示一条消息,该命令的名字为message,其参数分为消息类型和消息本体(双引号包含,内部可用$引用变量)两部分。

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错误,终止所有处理过程

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

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

(4)举例:

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

5、变量(字符串)操作

(1)字符串追加:

        使用set命令拼接:

set(<变量名1> ${<变量名1>} ${<变量名2>} ...)
# 将从第二个参数开始往后所有的字符串按顺序进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据,则原数据被覆盖

        使用list命令拼接:

list(APPEND <list> [<element> ...])
# list的功能比较丰富,在第一个参数的位置指定需要进行的操作,APPEND表示数据追加
# <list>是拼接字符串的存储变量,<element>为被拼接的字符串

        在CMake中,可以使用set命令创建一个list(list本身也是变量),list的底层会用分号分割子串,这是为了list做分割字符串而设计的,用户并不可见,如果打印list的变量值,分号不会被打印出来

(2)字符串移除:

        使用list命令移除:

list(REMOVE_ITEM <list> <value> [<value> ...])
# 在第一个参数的位置指定需要进行的操作,REMOVE_ITEM表示数据移除
# <list>是当前操作的列表,<value>为需要移除的字符串
# 移除带路径的字符串时,需要注意,如果希望移除某个文件,但字符串中该文件是带路径的,那么<value>必须要将该文件的路径也指定出来,也就是说,需要移除的字符串,必须一字不落地体现在<value>中

(3)list命令的其它功能:

        获取list的长度:

list(LENGTH <list> <output variable>)
# <list>:当前操作的列表
# <output variable>:新创建的变量,用于存储列表的长度

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

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

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

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

        查找列表是否存在指定的元素:

list(FIND <list> <value> <output variable>)
# <list>:当前操作的列表
# <value>:需要在列表中搜索的元素
# <output variable>:新创建的变量
# 如果列表<list>中存在<value>,那么返回<value>在列表中的索引,如果不存在则返回-1

        在list中指定的位置插入若干元素:

list(INSERT <list> <element_index> <element> [<element> ...])
# <list>:当前操作的列表
# <element_index>:需要插入元素位置的索引
# <element>:插入的元素值

        将元素插入到列表的0索引位置:

list (PREPEND <list> [<element> ...])
# <list>:当前操作的列表
# <element>:插入的元素值

        将列表中最后元素移除:

list (POP_BACK <list> [<out-var>...])
# <list>:当前操作的列表

        将列表中第一个元素移除:

list (POP_FRONT <list> [<out-var>...])
# <list>:当前操作的列表

        将指定索引的元素从列表中移除:

list (REMOVE_AT <list> <index> [<index> ...])
# <list>:当前操作的列表
# <index>:需要移除元素位置的索引

        移除列表中的重复元素:

list (REMOVE_DUPLICATES <list>)
# <list>:当前操作的列表

        列表翻转:

list (REVERSE <list>)
# <list>:当前操作的列表

        列表排序:

list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
# <list>:当前操作的列表
# [[	COMPARE:指定排序方法,有如下几种值可选
            STRING:按照字母顺序进行排序,为默认的排序方法
            FILE_BASENAME:如果是一系列路径名,会使用basename进行排序
            NATURAL:使用自然数顺序排序
        CASE:指明是否大小写敏感,有如下几种值可选
            SENSITIVE:按照大小写敏感的方式进行排序,为默认值
            INSENSITIVE:按照大小写不敏感方式进行排序
        ORDER:指明排序的顺序,有如下几种值可选
            ASCENDING:按照升序排列,为默认值
            DESCENDING:按照降序排列
]]

6、宏定义

(1)在进行程序测试的时候,通常会在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,如下所示。

#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;
}

(2)上面的代码中,对DEBUG宏进行了判断,这个宏是在代码中定义的,为了让测试更灵活,这个宏可以不在代码中定义,而是在测试的时候把它定义出来,其中一种方式就是在gcc/g++命令中指定,比如:

$ gcc test.c -DDEBUG -o app
# 在gcc/g++命令中通过参数-D指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏,名字为DEBUG

(3)既然可以通过gcc/g++命令定义宏,那么在CMake中也同样可以实现,对应的命令是add_definitions。

add_definitions(-D<宏名称>)

(4)gcc/g++命令定义宏举例:

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

(5)CMake中一些常用的宏:

功能

PROJECT_SOURCE_DIR

使用cmake命令后紧跟的目录,一般是工程的根目录

PROJECT_BINARY_DIR

执行cmake命令的目录

CMAKE_CURRENT_SOURCE_DIR

当前处理的CMakeLists.txt所在的路径

CMAKE_CURRENT_BINARY_DIR

target 编译目录

EXECUTABLE_OUTPUT_PATH

重新定义目标二进制可执行文件的存放位置

LIBRARY_OUTPUT_PATH

重新定义目标链接库文件的存放位置

PROJECT_NAME

返回通过PROJECT指令定义的项目名称

CMAKE_BINARY_DIR

项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径

7、嵌套CMake

(1)如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件(头文件目录不需要),由根节点的CMakeLists.txt统一调用,这样每个文件都不会太复杂,而且更灵活,更容易维护。(和makefile嵌套调用的思想是一致的)

(2)节点关系:

①Linux的目录是树状结构,所以嵌套的CMake也是一个树状结构,最顶层的CMakeLists.txt是根节点,其次都是子节点。

②根节点CMakeLists.txt中的变量全局有效,父节点CMakeLists.txt中的变量可以在子节点中使用,子节点CMakeLists.txt中的变量只能在当前节点中使用。

③CMake中父子节点之间的关系使用add_subdirectory命令建立(命令由父节点执行)。

add_subdirectory(<source_dir> [<binary_dir>] [EXCLUDE_FROM_ALL])
# <source_dir>:指定子节点CMakeLists.txt的位置,其实就是指定子目录
# <binary_dir>:指定输出文件的路径,一般不需要指定,忽略即可
# EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外;用户必须显式构建在子路径下的目标

(3)举例:

①本例工程的目录结构如下:

②对于calc和sort目录中的源文件来说,可以将它们先编译成库文件(可以是静态库,也可以是动态库),然后在提供给测试文件使用即可。

③根目录中的CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(test)
# 定义全局变量
# 静态库生成的路径
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 测试程序生成的路径
set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
# 头文件目录
set(HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 静态库的名字
set(CALC_LIB calc)
set(SORT_LIB sort)
# 可执行程序的名字
set(APP_NAME_1 test1)
set(APP_NAME_2 test2)
# 添加子目录(建立父子节点关系)
add_subdirectory(calc)
add_subdirectory(sort)
add_subdirectory(test1)
add_subdirectory(test2)

④calc目录和sort目录中的CMakeLists.txt文件:

[1]calc目录中的CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC)   # 搜索当前目录(calc目录)下的所有源文件,存于SRC
include_directories(${HEAD_PATH})        	# 包含头文件路径
set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) 	# 设置库的生成的路径
add_library(${CALC_LIB} STATIC ${SRC})	# 生成静态库

[2]sort目录中的CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./ SRC)   # 搜索当前目录(sort目录)下的所有源文件,存于SRC
include_directories(${HEAD_PATH})        	# 包含头文件路径
set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) # 设置库的生成的路径
add_library(${SORT_LIB} SHARED ${SRC})	# 生成动态库

⑤test1目录和test2目录中的CMakeLists.txt文件:

[1]test1目录中的CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALCTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})       	 	# 包含头文件路径
link_directories(${LIB_PATH})
link_libraries(${CALC_LIB})					# 指定可执行程序要链接的静态库
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})  # 指定可执行程序生成的路径
add_executable(${APP_NAME_1} ${SRC})		# 生成可执行程序

[2]test2目录中的CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(SORTTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})       	 		# 包含头文件路径
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})	# 指定可执行程序生成的路径
link_directories(${LIB_PATH})			# 指定可执行程序要链接的动态库的路径
add_executable(${APP_NAME_2} ${SRC})	# 生成可执行程序
target_link_libraries(${APP_NAME_2} ${SORT_LIB})	# 指定可执行程序要链接的动态库的名字

⑥以上内容完成后,进入根结点目录的build目录中,执行“cmake ..”命令,然后在build目录下执行“make”命令,即可得到如下内容。

8、CMake中的条件判断

(1)条件判断的语法格式:

if(<condition1>)
  <commands1>		# 满足condition1则执行commands1
elseif(<condition>)		# 可选块,可以有多个elseif
  <commands2>		# 不满足condition1、满足condition2则执行commands2
else()              	# 可选块
  <commands3>		# 不满足condition1、不满足condition2则执行commands3
endif()

(2)基本表达式:

if(<expression>)
# <expression>有三种情况——常量、变量、字符串
# 如果是1、ON、YES、TRUE、Y、非零值、非空字符串时,条件判断返回True
# 如果是0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串时,条件判断返回False

(3)逻辑判断运算:

①非运算NOT:

if(NOT <condition>)
# 如果条件<condition>为True将返回False,如果条件<condition>为False将返回True

②且运算AND:

if(<cond1> AND <cond2>)
# 如果cond1和cond2同时为True,则返回True,否则返回False

③或运算OR:

if(<cond1> OR <cond2>)
# 如果cond1和cond2至少有一个为True,则返回True,否则返回False

(4)比较运算:

①基于数值的比较:

# 如果左侧数值小于右侧,返回True,否则返回False
if(<variable|string> LESS <variable|string>)	

# 如果左侧数值大于右侧,返回True,否则返回False
if(<variable|string> GREATER <variable|string>)

# 如果左侧数值等于右侧,返回True,否则返回False
if(<variable|string> EQUAL <variable|string>)

# 如果左侧数值小于等于右侧,返回True,否则返回False
if(<variable|string> LESS_EQUAL <variable|string>)

# 如果左侧数值大于等于右侧,返回True,否则返回False
if(<variable|string> GREATER_EQUAL <variable|string>)

②基于字符串的比较(从首字母开始比较ASCII码大小):

# 如果左侧字符串小于右侧,返回True,否则返回False
if(<variable|string> STRLESS <variable|string>)

# 如果左侧字符串大于右侧,返回True,否则返回False
if(<variable|string> STRGREATER <variable|string>)

# 如果左侧字符串等于右侧,返回True,否则返回False
if(<variable|string> STREQUAL <variable|string>)

# 如果左侧字符串小于等于右侧,返回True,否则返回False
if(<variable|string> STRLESS_EQUAL <variable|string>)

# 如果左侧字符串大于等于右侧,返回True,否则返回False
if(<variable|string> STRGREATER_EQUAL <variable|string>)

(5)文件相关判断:

①判断文件或目录是否存在:

if(EXISTS <path-to-file-or-directory>)
# 如果文件或者目录存在则返回True,否则返回False

②判断是否是目录:

if(IS_DIRECTORY <path>)
# 此处的<path>必须是绝对路径
# 如果目录存在则返回True,目录不存在返回False

③判断是否是软链接(相当于Windows中的快捷方式):

if(IS_SYMLINK <file-name>)
# 此处的<file-name>必须是绝对路径
# 如果是软链接则返回True,否则返回False

④判断是否是绝对路径:

if(IS_ABSOLUTE <path>)
# 如果是Linux,该路径需要从根目录开始描述;如果是Windows,该路径需要从盘符开始描述
# 如果是绝对路径则返回True,否则返回False

⑤比较两个路径是否相等:

if(<variable|string> PATH_EQUAL <variable|string>)
# CMake 版本要求大于等于3.24
# 如果两个路径相等,则返回True,否则返回False

(6)判断某个元素是否在列表中:

if(<variable|string> IN_LIST <variable>)
# CMake 版本要求大于等于3.3
# 如果元素在列表中,则返回True,否则返回False

9、CMake中的循环

(1)使用foreach进行循环:

①语法格式:

foreach(<loop_var> <items>)	
    <commands>
endforeach()
# 对items中的数据进行遍历,然后通过loop_var将遍历到的当前的值取出,每取出一个数据,执行一次<commands>

②取值用法1:

命令格式:foreach(<loop_var> RANGE <stop>)

        RANGE:关键字,表示要遍历范围,步长为1

        stop:这是一个正整数,表示范围的结束值,在遍历的时候从0开始,最大值为stop

        loop_var:存储每次循环取出的值

        举例:

cmake_minimum_required(VERSION 3.2)
project(test)
# 循环
foreach(item RANGE 10)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

③取值用法2:

命令格式:foreach(<loop_var> RANGE <start> <stop> [<step>])

        RANGE:关键字,表示要遍历范围

        start:这是一个正整数,表示范围的起始值,也就是说最小值为start

        stop:这是一个正整数,表示范围的结束值,也就是说最大值为stop

        step:控制每次遍历的时候以怎样的步长增长,默认为1,可以不设置

        loop_var:存储每次循环取出的值

        举例:

cmake_minimum_required(VERSION 3.2)
project(test)

foreach(item RANGE 10 30 2)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

④取值用法3:

命令格式:foreach(<loop_var> IN [LISTS [<list>...]] [ITEMS [<item>...]])

        IN:关键字,表示在xxx里边

        LISTS:关键字,后面接的是列表变量名,可以有多个

        ITEMS:关键字,后面接的是变量或常量,可以有多个,变量需要通过$取值

        loop_var:存储每次循环取出的值(以单个项为单位,遍历LISTS/ITEMS列出的列表及单个项)

        举例:

cmake_minimum_required(VERSION 3.2)
project(test)
# 创建list
set(WORD a b c "d e f")  # 如果某个字符串中有空格,可以通过双引号将其包裹起来
set(NAME ace sabo luffy)
# 遍历list
foreach(item IN LISTS WORD NAME)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()
# 再遍历list
foreach(item IN ITEMS ${WORD} ${NAME})
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

⑤取值用法4(要求CMake的版本大于等于3.17):

命令格式:foreach(<loop_var>... IN ZIP_LISTS <list>... )

        IN:关键字,表示在xxx里边

        ZIP_LISTS:关键字,对应的是列表list

        loop_var:存储每次循环取出的值,可以根据要遍历的列表的数量指定多个变量,用于存储对应的列表当前取出的那个值

                如果指定了多个变量名,它们的数量应该和列表的数量相等;如果只指定一个变量名,CMake自动生成一系列的loop_var_N变量来存储对应列表中的当前项,loop_var_0对应第一个列表,loop_var_1对应第二个列表,以此类推

                如果遍历的多个列表中有一个列表较短,当它遍历完成之后,将不会再参与后续的遍历(因为其它列表还没有遍历完)

        举例:

cmake_minimum_required(VERSION 3.17)
project(test)
# 通过list给列表添加数据
list(APPEND WORD hello world "hello world")
list(APPEND NAME ace sabo luffy zoro sanji)
# 遍历列表
foreach(item1 item2 IN ZIP_LISTS WORD NAME)
    message(STATUS "当前遍历的值为: item1 = ${item1}, item2=${item2}" )
endforeach()

message("=============================")
# 遍历列表
foreach(item IN ZIP_LISTS WORD NAME)
    message(STATUS "当前遍历的值为: item1 = ${item_0}, item2=${item_1}" )
endforeach()

(2)使用while进行循环:

①语法格式:

while(<condition>)
    <commands>
endwhile()

②举例:

cmake_minimum_required(VERSION 3.5)
project(test)
# 创建一个列表 NAME
set(NAME luffy sanji zoro nami robin)
# 得到列表长度
list(LENGTH NAME LEN)
# 循环
while(${LEN} GREATER  0)
    message(STATUS "names = ${NAME}")
    # 弹出列表头部元素
    list(POP_FRONT NAME)
    # 更新列表长度
    list(LENGTH NAME LEN)
endwhile()
内容概要:本文围绕“基于最优控制的固定翼飞机着陆控制器设计”展开研究,利用Matlab代码实现相关控制算法的仿真与验证。研究聚焦于飞行器在着陆阶段的动力学建模与最优控制策略设计,通过构建精确的六自由度非线性运动学与动力学模型,结合现代控制理论中的线性次型调节器(LQR)等最优控制方法,设计出能够有效提升着陆精度、稳定性和抗干扰能力的自动着陆控制器。文中系统阐述了飞行器建模、平衡点分析、小扰动线性化、控制律设计、仿真环境搭建及多工况下的动态响应与性能指标分析全过程,旨在为航空器自动着陆系统的设计与优化提供坚实的理论依据和技术参考。; 适合人群:具备自动控制理论基础、飞行力学背景及Matlab/Simulink仿真能力的高校研究生、科研人员及航空航天领域工程师。; 使用场景及目标:①用于固定翼飞机自动着陆系统的设计与仿真验证;②作为最优控制理论在高阶复杂非线性系统中应用的教学案例;③为飞行控制算法的工程化研究与开发提供完整的技术路线与实现范例。; 阅读建议:建议读者结合Matlab代码与文中理论推导同步阅读,重点关注系统建模的物理假设、线性化条件、控制目标设定及多维度仿真结果的动态响应分析,有条件者可自行复现仿真以深化对最优控制策略设计与系统性能评估的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值