CMake学习(下)

1. 嵌套的CMake

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

先来看一下下面的这个的目录结构:

$ tree
.
├── build
├── calc
│   ├── add.cpp
│   ├── CMakeLists.txt
│   ├── div.cpp
│   ├── mult.cpp
│   └── sub.cpp
├── CMakeLists.txt
├── include
│   ├── calc.h
│   └── sort.h
├── sort
│   ├── CMakeLists.txt
│   ├── insert.cpp
│   └── select.cpp
├── test1
│   ├── calc.cpp
│   └── CMakeLists.txt
└── test2
    ├── CMakeLists.txt
    └── sort.cpp

6 directories, 15 files
  • include 目录:头文件目录
  • calc 目录:目录中的四个源文件对应的加、减、乘、除算法
    • 对应的头文件是include中的calc.h
  • sort 目录 :目录中的两个源文件对应的是插入排序和选择排序算法
    • 对应的头文件是include中的sort.h
  • test1 目录:测试目录,对加、减、乘、除算法进行测试
  • test2 目录:测试目录,对排序算法进行测试

可以看到各个源文件目录所需要的CMakeLists.txt文件现在已经添加完毕了。接下来庖丁解牛,我们依次分析一下各个文件中需要添加的内容。

1.1 准备工作

1.1.1 节点关系

众所周知,Linux的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:

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

1.1.2 添加子目录

接下来我们还需要知道在 CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令:

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

通过这种方式CMakeLists.txt文件之间的父子关系就被构建出来了。

1.2 解决问题

在上面的目录中我们要做如下事情:

  1. 通过 test1 目录中的测试文件进行计算器相关的测试
  2. 通过 test2 目录中的测试文件进行排序相关的测试
    现在相当于是要进行模块化测试,对于calcsort目录中的源文件来说,可以将它们先编译成库文件(可以是静态库也可以是动态库)然后在提供给测试文件使用即可。库文件的本质其实还是代码,只不过是从文本格式变成了二进制格式。

1.2.1 根目录

根目录中的 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)

在根节点对应的文件中主要做了两件事情:定义全局变量添加子目录

定义的全局变量主要是给子节点使用,目的是为了提高子节点中的CMakeLists.txt文件的可读性和可维护性,避免冗余并降低出差的概率。
一共添加了四个子目录,每个子目录中都有一个CMakeLists.txt文件,这样它们的父子关系就被确定下来了。

1.2.2 calc 目录

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

cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${CALC_LIB} STATIC ${SRC})

第3行aux_source_directory:搜索当前目录(calc目录)下的所有源文件
第4行include_directories:包含头文件路径,HEAD_PATH是在根节点文件中定义的
第5行set:设置库的生成的路径,LIB_PATH是在根节点文件中定义的
第6行add_library生成静态库,静态库名字CALC_LIB是在根节点文件中定义的

1.2.3 sort 目录

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

cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${SORT_LIB} SHARED ${SRC})
  • 第6行add_library生成动态库,动态库名字SORT_LIB是在根节点文件中定义的
    这个文件中的内容和calc节点文件中的内容类似,只不过这次生成的是动态库。

在生成库文件的时候,这个库可以是静态库也可以是动态库,一般需要根据实际情况来确定。如果生成的库比较大,建议将其制作成动态库。

1.2.4 test1 目录

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})
  • 第4行include_directories:指定头文件路径,HEAD_PATH变量是在根节点文件中定义的
  • 第6行link_libraries:指定可执行程序要链接的静态库,CALC_LIB变量是在根节点文件中定义的
  • 第7行set:指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
  • 第8行add_executable:生成可执行程序,APP_NAME_1变量是在根节点文件中定义的

此处的可执行程序链接的是静态库,最终静态库会被打包到可执行程序中,可执行程序启动之后,静态库也就随之被加载到内存中了。

1.2.5 test2 目录

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})
  • 第四行include_directories:包含头文件路径,HEAD_PATH变量是在根节点文件中定义的
  • 第五行set:指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
  • 第六行link_directories:指定可执行程序要链接的动态库的路径,LIB_PATH变量是在根节点文件中定义的
  • 第七行add_executable:生成可执行程序,APP_NAME_2变量是在根节点文件中定义的
  • 第八行target_link_libraries:指定可执行程序要链接的动态库的名字

在生成可执行程序的时候,动态库不会被打包到可执行程序内部。当可执行程序启动之后动态库也不会被加载到内存,只有可执行程序调用了动态库中的函数的时候,动态库才会被加载到内存中,且多个进程可以共用内存中的同一个动态库,所以动态库又叫共享库。

1.2.6 构建项目

一切准备就绪之后,开始构建项目,进入到根节点目录的build 目录中,执行cmake 命令,如下:

$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/cmake/calc/build

可以看到在build目录中生成了一些文件和目录,如下所示:

$ tree build -L 1     
build
├── calc                  # 目录
├── CMakeCache.txt        # 文件
├── CMakeFiles            # 目录
├── cmake_install.cmake   # 文件
├── Makefile              # 文件
├── sort                  # 目录
├── test1                 # 目录
└── test2                 # 目录

然后在build 目录下执行make 命令:
在这里插入图片描述
通过上图可以得到如下信息:

  1. 在项目根目录的lib目录中生成了静态库libcalc.a
  2. 在项目根目录的lib目录中生成了动态库libsort.so
  3. 在项目根目录的bin目录中生成了可执行程序test1
  4. 在项目根目录的bin目录中生成了可执行程序test2

最后再来看一下上面提到的这些文件是否真的被生成到对应的目录中了:

$ tree bin/ lib/
bin/
├── test1
└── test2
lib/
├── libcalc.a
└── libsort.so

由此可见,真实不虚,至此,项目构建完毕。

写在最后:

在项目中,如果将程序中的某个模块制作成了动态库或者静态库并且在CMakeLists.txt 中指定了库的输出目录,而后其它模块又需要加载这个生成的库文件,此时直接使用就可以了,如果没有指定库的输出路径或者需要直接加载外部提供的库文件,此时就需要使用 link_directories 将库文件路径指定出来。

2. 流程控制

在 CMake 的 CMakeLists.txt 中也可以进行流程控制,也就是说可以像写 shell 脚本那样进行条件判断循环

2.1 条件判断

关于条件判断其语法格式如下:

if(<condition>)
  <commands>
elseif(<condition>) # 可选快, 可以重复
  <commands>
else()              # 可选快
  <commands>
endif()

在进行条件判断的时候,如果有多个条件,那么可以写多个elseif,最后一个条件可以使用else,但是开始和结束是必须要成对出现的,分别为:ifendif

2.1.1 基本表达式

if(<expression>)

如果是基本表达式,expression 有以下三种情况:常量、变量、字符串

  • 如果是1, ON, YES, TRUE, Y, 非零值,非空字符串时,条件判断返回True
  • 如果是 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串时,条件判断返回False

2.1.2 逻辑判断

  • NOT
if(NOT <condition>)

其实这就是一个取反操作,如果条件conditionTrue将返回False,如果条件conditionFalse将返回True

  • AND
if(<cond1> AND <cond2>)

如果cond1cond2同时为True,返回True否则返回False

  • OR
if(<cond1> OR <cond2>)

如果cond1cond2两个条件中至少有一个为True,返回True,如果两个条件都为False则返回False

2.1.3 比较

  • 基于数值的比较
if(<variable|string> LESS <variable|string>)
if(<variable|string> GREATER <variable|string>)
if(<variable|string> EQUAL <variable|string>)
if(<variable|string> LESS_EQUAL <variable|string>)
if(<variable|string> GREATER_EQUAL <variable|string>)
  • LESS:如果左侧数值小于右侧,返回True
  • GREATER:如果左侧数值大于右侧,返回True
  • EQUAL:如果左侧数值等于右侧,返回True
  • LESS_EQUAL:如果左侧数值小于等于右侧,返回True
  • GREATER_EQUAL:如果左侧数值大于等于右侧,返回True
  • 基于字符串的比较
if(<variable|string> STRLESS <variable|string>)
if(<variable|string> STRGREATER <variable|string>)
if(<variable|string> STREQUAL <variable|string>)
if(<variable|string> STRLESS_EQUAL <variable|string>)
if(<variable|string> STRGREATER_EQUAL <variable|string>)
  1. STRLESS:如果左侧字符串小于右侧,返回True
  2. STRGREATER:如果左侧字符串大于右侧,返回True
  3. STREQUAL:如果左侧字符串等于右侧,返回True
  4. STRLESS_EQUAL:如果左侧字符串小于等于右侧,返回True
  5. STRGREATER_EQUAL:如果左侧字符串大于等于右侧,返回True

2.1.4 文件操作

  1. 判断文件或者目录是否存在
if(EXISTS path-to-file-or-directory)

如果文件或者目录存在返回True,否则返回False

  1. 判断是不是目录
if(IS_DIRECTORY path)
  • 此处目录的 path 必须是绝对路径
  • 如果目录存在返回True,目录不存在返回False。
  1. 判断是不是软连接
if(IS_SYMLINK file-name)
  • 此处的 file-name 对应的路径必须是绝对路径
  • 如果软链接存在返回True,软链接不存在返回False。
  • 软链接相当于 Windows 里的快捷方式
  1. 判断是不是绝对路径
if(IS_ABSOLUTE path)
  • 关于绝对路径:
    • 如果是Linux,该路径需要从根目录开始描述
    • 如果是Windows,该路径需要从盘符开始描述
  • 如果是绝对路径返回True,如果不是绝对路径返回False。

2.1.5 其它

  • 判断某个元素是否在列表中
if(<variable|string> IN_LIST <variable>)
  • CMake 版本要求:大于等于3.3
  • 如果这个元素在列表中返回True,否则返回False。
  • 比较两个路径是否相等
if(<variable|string> PATH_EQUAL <variable|string>)
  • CMake 版本要求:大于等于3.24
  • 如果这个元素在列表中返回True,否则返回False

关于路径的比较其实就是另个字符串的比较,如果路径格式书写没有问题也可以通过下面这种方式进行比较:

if(<variable|string> STREQUAL <variable|string>)

我们在书写某个路径的时候,可能由于误操作会多写几个分隔符,比如把/a/b/c写成/a//b///c,此时通过STREQUAL对这两个字符串进行比较肯定是不相等的,但是通过PATH_EQUAL去比较两个路径,得到的结果确实相等的,可以看下面的例子:

cmake_minimum_required(VERSION 3.26)
project(test)

if("/home//robin///Linux" PATH_EQUAL "/home/robin/Linux")
    message("路径相等")
else()
    message("路径不相等")
endif()

message(STATUS "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")

if("/home//robin///Linux" STREQUAL "/home/robin/Linux")
    message("路径相等")
else()
    message("路径不相等")
endif()

输出的日志信息如下:

路径相等
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
路径不相等

通过得到的结果我们可以得到一个结论:在进行路径比较的时候,如果使用 PATH_EQUAL 可以自动剔除路径中多余的分割线然后再进行路径的对比,使用 STREQUAL 则只能进行字符串比较。

2.2 循环

在 CMake 中循环有两种方式,分别是:foreachwhile

2.2.1 foreach

使用 foreach 进行循环,语法格式如下:

foreach(<loop_var> <items>)
    <commands>
endforeach()

通过foreach我们就可以对items中的数据进行遍历,然后通过loop_var将遍历到的当前的值取出,在取值的时候有以下几种用法:
方法1

foreach(<loop_var> RANGE <stop>)
  • RANGE:关键字,表示要遍历范围
  • stop:这是一个正整数,表示范围的结束值,在遍历的时候从 0 开始,最大值为 stop。
  • loop_var:存储每次循环取出的值

举例说明:

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

输出的日志信息是这样的:

$ cmake ..
-- 当前遍历的值为: 0
-- 当前遍历的值为: 1
-- 当前遍历的值为: 2
-- 当前遍历的值为: 3
-- 当前遍历的值为: 4
-- 当前遍历的值为: 5
-- 当前遍历的值为: 6
-- 当前遍历的值为: 7
-- 当前遍历的值为: 8
-- 当前遍历的值为: 9
-- 当前遍历的值为: 10
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

再次强调:在对一个整数区间进行遍历的时候,得到的范围是这样的 【0,stop】,右侧是闭区间包含 stop 这个值。

方法2

foreach(<loop_var> RANGE <start> <stop> [<step>])

这是上面方法1的加强版,我们在遍历一个整数区间的时候,除了可以指定起始范围,还可以指定步长。

  • 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()

输出的结果如下:

$ cmake ..
-- 当前遍历的值为: 10
-- 当前遍历的值为: 12
-- 当前遍历的值为: 14
-- 当前遍历的值为: 16
-- 当前遍历的值为: 18
-- 当前遍历的值为: 20
-- 当前遍历的值为: 22
-- 当前遍历的值为: 24
-- 当前遍历的值为: 26
-- 当前遍历的值为: 28
-- 当前遍历的值为: 30
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

再次强调:在使用上面的方式对一个整数区间进行遍历的时候,得到的范围是这样的 【start,stop】,左右两侧都是闭区间,包含 start 和 stop 这两个值,步长 step 默认为1,可以不设置。
方法3

foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])

这是foreach的另一个变体,通过这种方式我们可以对更加复杂的数据进行遍历,前两种方式只适用于对某个正整数范围内的遍历。

  • IN:关键字,表示在 xxx 里边
  • LISTS:关键字,对应的是列表list,通过set、list可以获得
  • ITEMS:关键字,对应的也是列表
  • loop_var:存储每次循环取出的值
cmake_minimum_required(VERSION 3.2)
project(test)
# 创建 list
set(WORD a b c d)
set(NAME ace sabo luffy)
# 遍历 list
foreach(item IN LISTS WORD NAME)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

在上面的例子中,创建了两个 list 列表,在遍历的时候对它们两个都进行了遍历(可以根据实际需求选择同时遍历多个或者只遍历一个)。输出的日志信息如下:

$ cd build/
$ cmake ..
-- 当前遍历的值为: a
-- 当前遍历的值为: b
-- 当前遍历的值为: c
-- 当前遍历的值为: d
-- 当前遍历的值为: ace
-- 当前遍历的值为: sabo
-- 当前遍历的值为: luffy
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

一共输出了7个字符串,说明遍历是没有问题的。接下来看另外一种方式:

cmake_minimum_required(VERSION 3.2)
project(test)

set(WORD a b c "d e f")
set(NAME ace sabo luffy)
foreach(item IN ITEMS ${WORD} ${NAME})
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

在上面的例子中,遍历过程中将关键字LISTS改成了ITEMS,后边跟的还是一个或者多个列表,只不过此时需要通过${}将列表中的值取出。其输出的信息和上一个例子是一样的:

$ cd build/
$ cmake ..
-- 当前遍历的值为: a
-- 当前遍历的值为: b
-- 当前遍历的值为: c
-- 当前遍历的值为: d e f
-- 当前遍历的值为: ace
-- 当前遍历的值为: sabo
-- 当前遍历的值为: luffy
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

小细节:在通过 set 组织列表的时候,如果某个字符串中有空格,可以通过双引号将其包裹起来,具体的操作方法可以参考上面的例子。

方法4
注意事项:这种循环方式要求CMake的版本大于等于 3.17。

foreach(<loop_var>... IN ZIP_LISTS <lists>)

通过这种方式,遍历的还是一个或多个列表,可以理解为是方式3的加强版。因为通过上面的方式遍历多个列表,但是又想把指定列表中的元素取出来使用是做不到的,在这个加强版中就可以轻松实现。

  • loop_var:存储每次循环取出的值,可以根据要遍历的列表的数量指定多个变量,用于存储对应的列表当前取出的那个值。
    • 如果指定了多个变量名,它们的数量应该和列表的数量相等
    • 如果只给出了一个 loop_var,那么它将一系列的 loop_var_N 变量来存储对应列表中的当前项,也就是说 loop_var_0 对应第一个列表,loop_var_1 对应第二个列表,以此类推......
    • 如果遍历的多个列表中一个列表较短,当它遍历完成之后将不会再参与后续的遍历(因为其它列表还没有遍历完)。
  • IN:关键字,表示在 xxx 里边
  • ZIP_LISTS:关键字,对应的是列表list,通过setlist可以获得
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()

在这个例子中关于列表数据的添加是通过list来实现的。在遍历列表的时候一共使用了两种方式,一种提供了多个变量来存储当前列表中的值,另一种只有一个变量,但是实际取值的时候需要通过变量名_0、变量名_1、变量名_N 的方式来操作,注意事项:第一个列表对应的编号是0,第一个列表对应的编号是0,第一个列表对应的编号是0。

上面的例子输出的结果如下:

$ cd build/
$ cmake ..
-- 当前遍历的值为: item1 = hello, item2=ace
-- 当前遍历的值为: item1 = world, item2=sabo
-- 当前遍历的值为: item1 = hello world, item2=luffy
-- 当前遍历的值为: item1 = , item2=zoro
-- 当前遍历的值为: item1 = , item2=sanji
=============================
-- 当前遍历的值为: item1 = hello, item2=ace
-- 当前遍历的值为: item1 = world, item2=sabo
-- 当前遍历的值为: item1 = hello world, item2=luffy
-- 当前遍历的值为: item1 = , item2=zoro
-- 当前遍历的值为: item1 = , item2=sanji
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/robin/abc/a/build

2.2.2 while

除了使用foreach也可以使用 while 进行循环,关于循环结束对应的条件判断的书写格式和if/elseif 是一样的。while的语法格式如下:

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

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

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()

输出的结果如下:

$ cd build/
$ cmake ..
-- names = luffy;sanji;zoro;nami;robin
-- names = sanji;zoro;nami;robin
-- names = zoro;nami;robin
-- names = nami;robin
-- names = robin
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/robin/abc/a/build

可以看到当列表中的元素全部被弹出之后,列表的长度变成了0,此时while循环也就退出了。

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

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

相关文章

携程旅行web逆向

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章…

C语言:volatile关键字讲解

读音&#xff1a;vaoletail C语言中的volatile关键字是一个重要的类型修饰符&#xff0c;它用于声明一个变量具有“易变性”&#xff0c;即可能在编译器无法察觉的情况下被改变其值。Volatile意思是“易变的”&#xff0c;应该解释为“直接存取原始内存地址”比较合适。 “易变…

【高质快刊】中科院1区TOP,最新案例仅2个月14天录用!进展超顺,即将截稿!

&#xff08;一&#xff09;期刊简介概况 【期刊类型】能源工程类SCIE&EI 【出版社】ELSEVIER出版社 【期刊概况】IF&#xff1a;11.0-12.0&#xff0c;JCR1区&#xff0c;中科院1区TOP 【预警情况】2020-2024年无预警记录 【收录年份】1977年被WOS数据库收录 【年发…

【python绘图colorbar对齐】

[Toc]# 1、问题描述 python在绘图过程中&#xff0c;可能会出现colorbar高度与主图不匹配情况&#xff0c;需要进行调整&#xff0c;使得与主图高度对齐&#xff0c;使图像更美观。示例&#xff1a;colorbar位置高于主图 2、解决方法 通过调整shrink参数匹配对齐,pad调整x轴…

【CPP】C++11多线程

thread类 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如windows和linux下各有自己的接口&#xff0c;这使得代码的可移植性比较差。C11中最重要的特性就是对线程进行支持了&#xff0c;使得C在并行编程时不需要依赖第三方库&#xff0c…

ARM中断实验

key_inc.c #include"key_inc.h"void key1_it_config(){//使能GPIOF外设时钟RCC->MP_AHB4ENSETR | (0x1<<5);//将PF9设置为输入模式GPIOF->MODER & (~(0x3<<18));//设置由PF9管脚产生EXTI9事件EXTI->EXTICR3 & (~(0XFF<<8));EXTI-…

Linux-线程同步

文章目录 前言一、为什么要线程同步&#xff1f;二、线程同步pthread_cond_initpthread_cond_destroypthread_cond_wait、pthread_cond_signal和 pthread_cond_broadcast 三、示例代码 前言 上节课学习了线程互斥&#xff0c;这节课针对线程互斥内容在做进一步的补充和完善&am…

鸿蒙Harmony应用开发—ArkTS(@State装饰器:组件内状态)

State装饰的变量&#xff0c;或称为状态变量&#xff0c;一旦变量拥有了状态属性&#xff0c;就和自定义组件的渲染绑定起来。当状态改变时&#xff0c;UI会发生对应的渲染改变。 在状态变量相关装饰器中&#xff0c;State是最基础的&#xff0c;使变量拥有状态属性的装饰器&a…

Elasticsearch:让你的 Elasticsearch 索引与 Python 和 Google Cloud Platform 功能保持同步

作者&#xff1a;来自 Elastic Garson Elasticsearch 内的索引 (index) 是你可以将数据存储在文档中的位置。 在使用索引时&#xff0c;如果你使用的是动态数据集&#xff0c;数据可能会很快变旧。 为了避免此问题&#xff0c;你可以创建一个 Python 脚本来更新索引&#xff0…

VMWare虚拟机使用openmediavault搭建NAS服务器完整步聚

下载: gopenmediavault - The open network attached storage solution 下载好openmediavault的ISO镜像后,打开虚拟机并安装 系统类型选择Debian 启动虚拟机并安装openmediavault 选择中文 地区选中国 键盘配置选汉语 开始安装 配置网络信息 配置root密码 确认密码 系统安装中…

LeetCode # 199. 二叉树的右视图

199. 二叉树的右视图 题目 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出: [1,3] 示例 3…

最新域名出售交易平台源码修复版

最新域名出售交易平台源码修复版 测试正常分享&#xff0c;保障你使用愉快&#xff0c;源码测试修复不易&#xff0c;拿走留痕是美德 无需到处找教程或修复&#xff0c;教程一次性到位&#xff0c;无需藏着掖着 最新修复版域名出售交易平台源码&#xff0c;搭建即可正常使用…

【群晖】Docker Compose部署 Emby Server

【群晖】Docker Compose部署 Emby Server 本来群晖上面的 Emby 是用套件安装的&#xff0c;但是不巧的是前两天脑袋一抽装了两个插件&#xff0c;导致 Emby Server被当肉鸡了&#xff0c;还找不到脚本代码在哪儿&#xff0c;一天时间上传了3T的流量。无奈之下&#xff0c;只能尝…

头条网盘拉新项目该怎么选择授权

作为十二月份首发上线的项目——头条网盘拉新。一经上线就受到很多想要做这行业人的关注&#xff0c;光是佣金已经是业内比较高的了&#xff01;每拉新一位新用户就可以获取到价格为9元一单的佣金&#xff0c;拉失活用户也可以获取价格为4元的佣金&#xff0c;推广方式和其他网…

真机笔记(2)项目分析

目录 1. 项目&#xff1a; 2. 网络工程师工作流程 3. 实验 设备命名 登录密码 使用SSH协议 1. 项目&#xff1a; 竞标方&#xff1a;集成商、厂商、代理商、服务商、监理检测公司 在一个网络项目中&#xff0c;不同的角色承担着不同的职责和任务。以下是集成商、厂商、代…

JavaScript-Web学习笔记01

一、Web APIs 1、Web API Web API 是浏览器提供的一套操作浏览器功能和页面元素的API&#xff08;BOM 和 DOM&#xff09;。 2、总结 API 是为我们提供的一个接口&#xff0c;帮助我们实现某种功能Web API 主要是针对浏览器提供的接口&#xff0c;主要针对浏览器做交互效果。W…

腾讯云COS - 前端上传文件到 COS 跨域问题

问题描述 原因分析 因为我本地的地址是&#xff1a;http://localhost:9528 而发送请求时的地址是&#xff1a;http://132-1307119153.cos.ap-beijing.myqcloud.com/tu.jpg 域名不同&#xff0c;自然而然就出现了跨域的问题&#xff01; 解决方案 先点击对象存储 - 安全设置…

吃瓜Grok大模型

段子区 今年当地时间2月29日晚&#xff0c;马斯克闹出来一件大事——正式起诉OpenAI和Sam Altman&#xff0c;并要求OpenAI 恢复开源GPT-4等模型。国际流量大师我只付服马斯克和川宝!&#xff01; 当大家觉得这扯皮的故事就此结束后&#xff0c;马斯克“不负众望”的整了一个大…

算法-图的强连通分量,图的最小生成树

1.图的强连通分量 (1). 定义 图的强连通分量是图论中的一个重要概念&#xff0c;主要在有向图中进行讨论。具体来说&#xff0c;如果在一个有向图G中&#xff0c;任意两个顶点vi和vj&#xff08;其中vi大于vj&#xff09;之间都存在一条从vi到vj的有向路径&#xff0c;同时也存…

解锁AI之门:协助探索Amazon Bedrock服务

AI愈加强大的功能和广泛的应用场景&#xff0c;正逐渐改变着我们的工作和生活方式。 Amazon Bedrock在AI的时代潮流中&#xff0c;也以其强大而灵活的功能特性&#xff0c;正在成为越来越多企业和个人的智能助手。 亚马逊云科技通过VERYCLOUD睿鸿股份的服务能力&#xff0c;使…