【C/C++进阶】CMake学习笔记

news2024/12/18 13:47:56

本篇文章包含的内容

  • 一、CMake简介
  • 二、使用CMake构建工程
    • 2.1 一个最简单的CMake脚本
    • 2.2 使用变量和宏
    • 2.3 文件搜索
  • 三、使用CMake制作和使用库文件
    • 3.1 静态库和动态库
    • 3.2 字符串操作
    • 3.3 CMake制作库文件
    • 3.4 CMake使用库文件
      • 3.4.1 使用`link_libraries`链接
      • 3.4.2 使用`target_link_libraries`链接
  • 四、CMake命令进阶
    • 4.1 CMake自定义宏
    • 4.2 CMake脚本嵌套
    • 4.3 其他的`list`命令


前言

博主在学习STM32和使用新版Vitis开发Zynq SoC的过程中,多次看到CMake的身影。CMake本身是在Makefile的基础上发展起来的强大的项目构建工具,多个厂家都力荐使用CMake构建工程。作为C/C++的使用者而言,学习编译工具和项目构建工具也非常重要。本篇文章记录了学习CMake时的学习笔记,欢迎大家留言探讨,共同进步。

  • OS:Ubuntu 20.04 WSL2
  • cmake版本:3.16.3(Ubuntu 20.04 WSL)
  • 参考课程连接:CMake 保姆级教程【C/C++】——爱编程的大丙

一、CMake简介

CMake是生成Makefile、Ninja等编译脚本的工具,同时也是强大的项目构建工具。CMake的主要目的是为了解决Makefile跨平台兼容性极差的问题。既然Makefile和操作系统强相关,那么我们就可以通过CMake来生成Makefile文件。

可以使用下面的命令检查系统中是否安装了CMake,并添加到环境变量中。博主的CMake是在winlibs中安装GCC+MinGw-w64时自带的。

cmake --version

在这里插入图片描述
但是本文章不在windows环境下运行。在学习阶段还是建议使用Linux系统。所以博主在Ubuntu 20.04 WSL环境下运行。

在Ubuntu 20.04下使用cmake,首先请确保cmake和GNU编译器已经正确安装:

sudo apt update
sudo apt upgrade

sudo apt install build-essential
sudo apt install cmake

二、使用CMake构建工程

2.1 一个最简单的CMake脚本

假设我们的工程结构如下所示。hello.cpp中打印hello world!并调用func.cpp中的函数print_message()

在这里插入图片描述

  • hello.cpp
#include <iostream>
#include "func.h"

using namespace std;

int main() {
    cout << "hello world!" << endl;

    print_message();

    return 0;
}
  • func.h
#ifndef __FUNC_H_
#define __FUNC_H_

#include <iostream>

void print_message(void);

#endif
  • func.cpp
#include "func.h"

void print_message(void) {
    std::cout << "msg from func.cpp" << std::endl;
}

第一步,首先在工程的根目录下建立CMakeLists.txt文件。注意文件名区分大小写,且不能出错。

第二步,在工程的根目录下建立build文件夹,之后可以使用cd命令进入该文件夹,使用cmake ..命令编译工程。这时cmake编译生成的中间文件就会自动存放在build文件夹下,保证根目录的整洁。

接下来就可以在CMakeLists.txt编写脚本了。

设置CMake使用的最小版本。这个版本必须西小于等于当前的CMake版本,例如这里我的CMake版本是3.16.3。

在这里插入图片描述

cmake_minimum_required(VERSION 3.16)

设置工程信息。除了设置工程的名称之外,还可以设置工程的版本、使用的编程语言等信息。

project(cmake_test_prj)

为工程添加可执行文件。第一个参数是可执行文件的名称hello,工程的可执行文件默认生成在CMakeLists.txt的同级目录下;之后的参数是需要编译的文件,只需要写.c或者.cpp的源文件即可,头文件不参与编译,所以在这里不需要指定。

add_executable(hello src/func.cpp src/hello.cpp)

如果可执行文件需要的头文件是由用户定义的,则我们需要额外指定头文件的目录。注意这里仅仅需要指定目录即可

include_directories(inc)

完成这些,我们的CMakeLists.txt文件应该如下所示:

cmake_minimum_required(VERSION 3.16)
project(cmake_test_prj)

include_directories(inc)
add_executable(hello src/func.cpp src/hello.cpp)

之后使用cd目录进入build目录,执行命令(..参数是CMakeLists.txt文件所在的目录,这里是上级目录):

cmake ..

在这里插入图片描述

之后执行下面的命令运行生成的Makefile文件进行编译(之后在不清除缓存的前提下可以在修改CMakeLists.txt后直接执行make命令,跳过cmake ..,默认自动生成一次Makefile脚本)。可以看到在build目录下生成了名为 hello 的可执行文件。使用./hello命令即可运行该文件。

make

在这里插入图片描述

2.2 使用变量和宏

使用set命令定义一个变量,并使用${}的格式使用这个变量的内容。CMake的变量定义时默认都是字符串类型。变量的名字可以是大写,也可以是小写,但是习惯上通常将变量记为大写来加以区分,增加CMakeLists.txt文件的可读性

set(SRC src/hello.cpp src/func.cpp)

add_executable(hello ${SRC})

宏是CMake预先定义的变量名,通过设置这些变量名,可以方便的控制程序的编译选项和配置。例如通过修改宏CMAKE_CXX_STANDARD可以更改编译使用的C/C++标准;使用EXECUTABLE_OUTPUT_PATH 修改可执行文件的生成路径:

# 使用 C/C++ 11 标准
set(CMAKE_CXX_STANDARD 11)
set(EXECUTABLE_OUTPUT_PATH <path>)

对于内置的宏,我们也可以直接通过${}的方式调用。下面展示两个宏,并说明它们的异同:

  • PROJECT_SOURCE_DIR:执行cmake ..命令的参数,以绝对路径存储;
  • CMAKE_SOURCE_DIR:当前运行的CMakeLists.txt文件的路径,以绝对路径存储。所以在这里这两个路径存储的值是一样的。

我们可以使用message()命令打印所需要的信息,检查他们的值:

message(PROJECT_SOURCE_DIR : ${PROJECT_SOURCE_DIR})
message(CMAKE_SOURCE_DIR : ${CMAKE_SOURCE_DIR})

在这里插入图片描述

2.3 文件搜索

此时我们的CMakeLists.txt文件是这样的:

cmake_minimum_required(VERSION 3.16)
project(cmake_test_prj)

set(SRC src/hello.cpp src/func.cpp)
include_directories(inc)

add_executable(hello ${SRC})

但是它明显没有解决当源文件过多时需要手写源文件的问题,并且虽然脚本中关于路径的设置都是从CMakeLists.txt文件所在的目录开始表示的,但是这样的路径的引用并不规范,某种程度上也不是很安全。

使用下面的方法找出src目录下所有的源文件,并存储在变量SRC中。

aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC)

或者使用下面的方法找出src目录下所有的.cpp文件,并存储在变量SRC中。使用file命令可以指定任意后缀的文件。GLOB参数指搜索当前路径,如果替换为GLOB_RECURSE表示递归搜索。*表示通配符。当然使用同样的方法也可搜索指定路径下所有的.h文件。

file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.cpp)

修改后,CMakeLists.txt可以修改的更加规范:

cmake_minimum_required(VERSION 3.16)

project(cmake_test_prj)

# file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.cpp)
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC)
include_directories(${PROJECT_SOURCE_DIR}/inc)

add_executable(hello ${SRC})

三、使用CMake制作和使用库文件

3.1 静态库和动态库

静态库和动态库都是编译生成的二进制源文件。它们和源代码唯一的不同在于库的代码是机器可以直接识别的,但是人并不能识别。从某种意义上说,库文件是对源文件的一种加密方式。静态库在链接时直接打包复制到可执行文件中,在执行时直接和可执行文件一起加载到内存(不考虑虚拟内存和Cache命中的简单模型);而动态库则在仅在程序执行时被调用时才会被加载到内存

在Linux系统中,静态库文件以.a(archive)结尾,动态库文件以.so(shared object)结尾。在windows系统中,静态库文件以.lib(library)结尾,动态库文件以.dll(dynamic linked library)结尾。无论在哪个系统中,库文件的名字由lib-xxx-后缀三部分完成,我们通过cmake指定库文件名称时指定的是中间xxx的内容。

制作库文件后,发布者需要发布库文件和库文件对应的头文件。头文件虽然不参与编译,但是头文件中的声明说明了源文件中存在哪些函数和接口可供调用,所以对于使用者而言是必不可少的信息。

3.2 字符串操作

CMake中的变量默认都以字符串类型存储。我们可以打印由aux_source_directory命令输出的SRC变量查看这个字符串:

message(${SRC})

在这里插入图片描述
需要注意的是,CMake存储的字符串和其他语言定义的字符串不同,CMake的字符串由一个个子字符串(item)构成,在底层实现中,各个子字符串用分号隔开区分,但是打印输出时并不显示这个分号。在查找(例如删除)时,遵循完全匹配原则,即CMake只会查找和被删除字符串完全相同的子字符串进行删除

  • 字符串拼接
set(val_name ${val1} ${val2} ...)
  • 字符串追加
list(APPEND val_name ${val1} ${val2} ...)
  • 字符串删除
list(REMOVE_ITEM val_name ${val1} ${val2} ...)

在制作库文件时,我们往往需要找到除main函数所在文件的其他文件作为源文件,这时候就可以使用字符串操作优雅地找到子字符串的名称并将其删除。

list(REMOVE_ITEM STC ${PROJECT_SOURCE_DIR}/src/hello.cpp)

在这里插入图片描述

3.3 CMake制作库文件

制作库文件不需要包含测试程序,即main函数存在的文件。

使用下面的命令制作一个库文件。STATIC/SHARED参数指定要生成的库文件类型,如果缺省该参数默认生成静态库文件。

add_library(lib_name STATIC/SHARED [SOURCE1] [SOURCE2] ...)

生成静态库的一个示例:

cmake_minimum_required(VERSION 3.16)

project(cmake_test_prj)

aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC)
include_directories(${PROJECT_SOURCE_DIR}/inc)

list(REMOVE_ITEM SRC ${PROJECT_SOURCE_DIR}/src/hello.cpp)

# add_executable(hello ${SRC})
add_library(test ${PROJECT_SOURCE_DIR}/src/func.cpp)

在这里插入图片描述
生成动态库的一个示例:

cmake_minimum_required(VERSION 3.16)

project(cmake_test_prj)

aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC)
include_directories(${PROJECT_SOURCE_DIR}/inc)

list(REMOVE_ITEM SRC ${PROJECT_SOURCE_DIR}/src/hello.cpp)

# add_executable(hello ${SRC})
add_library(test SHARED ${PROJECT_SOURCE_DIR}/src/func.cpp)

在这里插入图片描述

3.4 CMake使用库文件

为了测试我们生成的库文件是否正确,新建一个lib文件夹,并在其中建立两个子文件夹,分别将静态库文件和动态库文件放入对应的文件夹:

在这里插入图片描述
如果我们使用了第三方的库(或自定义),使用下面的命令在CMake中指定第三方库文件的路径。可以同时指定多个路径。

link_directories(...)

3.4.1 使用link_libraries链接

如果要在程序中使用静态库,则应该使用下面的命令指定使用的静态库名称。

link_libraries(lib_name)

注意:在课程中提到,link_libraries只能用来连接静态库,且该指令需要放在生成可执行程序add_executable之前。但是博主的环境中测试后,在这个简单的例子中就算使用该命令连接动态库文件(直接将路径指定为${PROJECT_SOURCE_DIR}/lib/shared),程序同样可以正常运行。

下面是一个加载静态库的CMakeLists.txt示例。注意在指定库文件的同时,头文件的路径依然需要我们指定。

cmake_minimum_required(VERSION 3.16)

project(cmake_test_prj)

include_directories(${PROJECT_SOURCE_DIR}/inc)

link_directories(${PROJECT_SOURCE_DIR}/lib/static)
link_libraries(test)

add_executable(hello ${PROJECT_SOURCE_DIR}/src/hello.cpp)

在这里插入图片描述

3.4.2 使用target_link_libraries链接

链接动态库常用的命令为target_link_libraries()(实际上,这个命令既可以链接静态库也可以链接动态库,一种说法是link_libraries()命令是老版本的命令,在较新的CMake版本中已经被舍弃了)。

target_link_libraries(
	<target> 
	<PUBLIC/PRIVATE/INTERFACE> <library 1> 
	<PUBLIC/PRIVATE/INTERFACE> <library 2>
)

这里的<target>可以是一个源文件,动态库文件或者可执行文件。使用动态库之前,需要首先明确一点:动态库链接时的链接权限具有可配置的传递性。可使用的权限分三种,分别是PUBLIC、PRIVATE、INTERFACE,三种权限对应的传递性依次降低。如果不指定权限,默认为PUBLIC。为了说明它们的区别,我在这里用一幅图说明:

在这里插入图片描述

在上面的例子中,我们使用一个可执行文件作为target,它链接到一个动态库A。而动态库A链接到其他2个动态库和一个静态库。动态库B的权限为PUBLIC,动态库C的权限为PRIVATE,静态库D的权限为INTERFACE。

  • 如果链接的权限为PUBLIC,则库的链接具有传递性。不仅动态库A中可以使用动态库B的变量和函数(方法),可执行文件中也可以使用动态库B的变量和函数(方法)。
  • 如果链接的权限为PRIVATE,则库的链接不具有传递性。仅动态库A可以使用动态库C中的变量和函数(方法),而可执行文件不可以。
  • INTERFACE target是一种特殊的target,通常适用于传递头文件信息和编译选项,但库本身并不需要生成任何的二进制文件的情况。如果链接的权限为INTERFACE,动态库A可以将动态库D的定义和编译选项作为接口传递给可执行文件,但A并不依赖库D生成任何的文件。当你为一个目标设置INTERFACE权限时,你实际上是在告诉CMake:“当其他目标链接到我时,它们需要这些设置,但我自己不需要”。

动态库又被称为共享目标(shared object)。每个程序在执行时,对应一个进程。这个进程存储在一块虚拟的地址空间中。计算机通过MMU(Memory Management Unit,内存管理单元)将虚拟地址空间的地址映射到物理内存上。虚拟地址空间优化了内存的使用效率,同时对数据的地址作了一层保护。如果在进程运行期间调用了动态库中的函数,则动态库会被加载到物理内存中。假设此时另一个进程也需要调用这个动态库,则这个动态库不需要被重新加载,而是直接到物理内存中使用。所以,无论何时,同一个动态库在物理内存中只会存在一份,这时两个进程就对这一个动态库实现了“共享”。

通常而言,target_link_libraries()命令写在生成可执行文件命令add_executable()或者生成库命令add_library()之后。

下面是一个链接动态库的示例:

cmake_minimum_required(VERSION 3.16)

project(cmake_test_prj)

include_directories(${PROJECT_SOURCE_DIR}/inc)

link_directories(${PROJECT_SOURCE_DIR}/lib/shared)
# link_libraries(test)

add_executable(hello ${PROJECT_SOURCE_DIR}/src/hello.cpp)
target_link_libraries(hello test)

在上面的示例中,我们仍然使用link_directories命令指定连接库的路径。但是这个命令有一个缺点:它会影响所有的目标搜寻的范围,如果存在多个目标,并且它们需要连接的库在不同的目录下,但是名称相同,使用link_directories就会造成一些混淆的错误。所以,在多数情况下,优先使用target_link_directories命令为不同的目标指定不同的路径。下面是修改后的示例:

cmake_minimum_required(VERSION 3.16)

project(cmake_test_prj)

include_directories(${PROJECT_SOURCE_DIR}/inc)

add_executable(hello ${PROJECT_SOURCE_DIR}/src/hello.cpp)
target_link_directories(hello PUBLIC ${PROJECT_SOURCE_DIR}/lib/shared)
target_link_libraries(hello test)

注意:不仅仅可执行程序可以链接库,库和库之间也可以链接。静态库可以链接其他的静态库,也可以链接其他的动态库;动态库可以链接其他的静态库,也可以链接其他的动态库。具体的操作和上面提及的例子大同小异,只需要把生成可执行程序的命令改成生成库的命令即可,在这里不再赘述,重点在于梳理不同库之间的链接关系。

四、CMake命令进阶

4.1 CMake自定义宏

在项目中,我们往往需要在开发阶段打印大量的日志信息,但是在发布时又不想让这些日志信息输出。为了方便,可以在程序中添加宏定义的方式决定哪些程序会被预处理:

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

using namespace std;

int main() {
    cout << "hello world!" << endl;

#ifdef DEBUG

    cout << "[DEBUG INFO] This is a DEBUG message." << endl;

#endif

    print_message();

    return 0;
}

为了使得命令生效,可以使用gcc/g++命令定义DEBUG宏:

在这里插入图片描述
既然可以通过命令行的方式定义,那么当然也可以在CMakeLists.txt中指定编译的宏。

add_definitions(-DDEBUG)

在这里插入图片描述

使用该命令可以一次指定多个编译的宏,每个宏之前都要加上-D前缀,并用空格或者换行分开:

add_definitions(-DFOO -DBAR)

4.2 CMake脚本嵌套

在实际的项目开发中,多模块开发是非常常见的。有些模块只需要生成静态库,有些模块需要生成用于测试的可执行程序。对于这些模块,我们可以分别对它们进行CMake脚本的编写,并且通过一个顶层的CMake脚本对这些子模块进行管理。一个CMakeLists.txt就对应一个节点,这样就形成了多个节点之间的父子关系。

在下面的例子中,工程根目录下是一个父节点,分别有calchello两个子节点。calc仅负责生成一个静态库,hello仅负责调用这个静态库并生成一个可执行文件用来测试,所以它们的CMake脚本可以独立编写。

在这里插入图片描述

修改后的工程结构可能和实际的项目开发有较大差距。博主水平有限,请见谅,在此仅为说明CMake在脚本嵌套上辨析的一些细节。

  • 修改后的inc/func.h
#ifndef __FUNC_H_
#define __FUNC_H_

#include <iostream>

void print_message(void);
int add_func(int a, int b);
int sub_func(int a, int b);

#endif
  • 修改后的hello/hello.cpp
#include <iostream>
#include "func.h"

using namespace std;

int main() {

    cout << "13 + 13 = " << add_func(13, 13) << endl;
    cout << "6 + 5 = " << sub_func(6, 5) << endl;

#ifdef DEBUG
    cout << "[DEBUG INFO] This is a DEBUG message." << endl;
#endif

    print_message();

    return 0;
}
  • 修改后的calc/add.cpp
#include "func.h"

int add_func(int a, int b) {
    return a + b;
}

int sub_func(int a, int b) {
    return a - b;
}

父节点通过add_subdirectory()命令为其自身添加子节点。子节点可以使用(继承)父节点中的变量。或者可以将子节点中的变量看作局部变量,将父节点中的变量看作全局变量。利用这个特性,通常可以在父节点中定义生成库的名称、路径;生成可执行文件的名称、路径;头文件路径等信息。

  • 父节点CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(cmake_test_prj)

set(INCPATH ${PROJECT_SOURCE_DIR}/inc)
set(EXECPATH ${PROJECT_SOURCE_DIR}/bin)
set(LIBPATH ${PROJECT_SOURCE_DIR}/lib)

add_subdirectory(calc)
add_subdirectory(hello)
  • calc/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(calc)

include_directories(${INCPATH})
aux_source_directory(./ SRC)
set(LIBRARY_OUTPUT_PATH ${LIBPATH})

add_library(calc STATIC ${SRC})
  • calc/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(hello)

include_directories(${INCPATH})
aux_source_directory(./ SRC)

link_directories(${LIBPATH})
link_libraries(calc)

set(EXECUTABLE_OUTPUT_PATH ${EXECPATH})

add_executable(hello ${SRC})

对于脚本中提及的可执行文件路径bin和库文件路径lib,我们事先不需要自己创建。执行CMake脚本后,不存在的路径会自动生成。之后即可执行make命令编译文件:

在这里插入图片描述
可以看到静态库文件和可执行程序正常生成,程序运行结果也是正常的。

在这里插入图片描述

4.3 其他的list命令

使用list命令还可以对字符串作更多操作,但是这些操作记忆起来相当繁杂,在此罗列,需要使用时查询即可。

CMake中一个字符串(list)的子字符串索引同样从0开始,-1指最后一个元素,以此类推。

  • 获取字符串的长度
list(LENGTH <list> <output variable>)
  • 获取字符串指定索引的元素
list(GET <list> <element index1> [<element index2> ...] <output variable>)
  • 使用特定连接符连接子字符串(即将隐式的;改为显式的某字符串)
list(JOIN <list> <glue> <output variable>)
  • 找到列表是否存在指定的元素,如果找到返回索引值,如果没找到返回-1
list(FIND <list> <value> <output variavle>)
  • 在指定索引之前添加多个元素
list(INSERT <list> <element_index> <element1> [<element2> ...])
  • 将指定元素添加到所有元素之前(插入到之后使用APPEND关键字)
list(PREPEND <list> <element1> [<element2> ...])
  • 弹出最后一个元素(输出变量可缺省)
list(POP_BACK <list> [<output variable>])
  • 弹出第一个元素(输出变量可缺省)
list(POP_FRONT <list> [<output variable>])
  • 删除指定索引的元素
list(REMOVE_AT <list> <index1> [<index2> ...])
  • 删除重复元素
list(REMOVE_DUPLICATES <list>)
  • 列表翻转
list(REVERSE <list>)
  • 列表排序
    • compare
      • STRING:默认方法,按字母顺序排序
      • FILE_BASENAME:如果是一系列路径名,按路径basename进行排序
      • NATURAL:按自然数方法进行排序
    • case:大小写是否敏感,分为SENSITIVE(默认)和INSENSITNVE
    • order:排列顺序,分为ASCENDING(默认)升序和DESCENDING降序
list(SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])

  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。


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

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

相关文章

JS 生成防篡改水印

网页中有水印的需求&#xff0c;今天我们实现手写一个防篡改水印&#xff0c;先看下效果图&#xff1a; 一、创建class函数 传递一个dom为水印包裹器&#xff0c;有一些监听防篡改的observer&#xff0c;然后实例化的时候创建水印&#xff0c;执行create()方法 class WaterMa…

概率论得学习和整理26:EXCEL 关于plot 折线图--频度折线图的一些细节

目录 0 折线图有很多 1 频度折线图 1.1 直接用原始数据做的频度折线图 2 将原始数据生成数据透视表 3 这样可以做出了&#xff0c;频度plot 4 做按某字段汇总&#xff0c;成为累计plot分布 5 修改上面显示效果&#xff0c;做成百分比累计plot频度分布 0 折线图有很多 这…

实现echart大屏动画效果及全屏布局错乱解决方式

如何实现echarts动画效果?如何实现表格或多个垂直布局的柱状图自动滚动效果?如何解决tooltip位置超出屏幕问题,如何解决legend文字过长,布局错乱问题?如何处理饼图的中心图片永远居中? 本文将主要解决以上问题,如有错漏,请指正. 一、大屏动画效果 这里的动画效果主要指&…

pytest入门九:feature

fixture是pytest特有的功能&#xff0c;用以在测试执行前和执行后进行必要的准备和清理工作。使用pytest.fixture标识&#xff0c;定义在函数前面。在你编写测试函数的时候&#xff0c;你可以将此函数名称做为传入参数&#xff0c;pytest将会以依赖注入方式&#xff0c;将该函数…

C# 中的闭包

文章目录 前言一、闭包的基本概念二、匿名函数中的闭包1、定义和使用匿名函数2、匿名函数捕获外部变量3、闭包的生命周期 三、Lambda 表达式中的闭包1、定义和使用 Lambda 表达式2、Lambda 表达式捕获外部变量3、闭包的作用域 四、闭包的应用场景1、事件处理2、异步编程3、迭代…

ChatGPT客户端安装教程(附下载链接)

用惯了各类AI的我们发现每天打开网页还挺不习惯和麻烦&#xff0c;突然发现客户端上架了&#xff0c;懂摸鱼的人都知道这里面的道行有多深&#xff0c;话不多说&#xff0c;开整&#xff01; 以下是ChatGPT客户端的详细安装教程&#xff0c;适用于Windows和Mac系统&#xff1a…

GRE over IPSec 如何应用?如何在ensp上配置GRE over IPSec 实验?

GRE over IPSec应用场景 IPSec VPN本端设备无法感知对端有几个设备 &#xff0c;本端共用一个IPSec SA 。报文封装中没有对端设备的下一跳 &#xff0c;所以无法传输组播、广播和非IP报文 &#xff0c;比如OSPF协议 &#xff0c;导致分支与总部的内部网络之间无法使用OSPF路由…

概率论得学习和整理29: 用EXCEL 描述二项分布

目录 1 关于二项分布的基本内容 2 二项分布的概率 2.1 核心要素 2.2 成功K次的概率&#xff0c;二项分布公式 2.3 期望和方差 2.4 具体试验 2.5 概率质量函数pmf 和cdf 3 二项分布的pmf图的改进 3.1 改进折线图 3.2 如何生成这种竖线图呢 4 不同的二项分布 4.1 p0.…

leetcode 面试经典 150 题:三数之和

链接三数之和题序号11类型数组解题方法排序双指针法难度中等 题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c; 同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三…

【Linux】Nginx一个域名https一个地址配置多个项目【项目实战】

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;CSDN博客专家   &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01…

【线性代数】理解矩阵乘法的意义(点乘)

刚接触线性代数时&#xff0c;很不理解矩阵乘法的计算规则&#xff0c;为什么规则定义的看起来那么有规律却又莫名其妙&#xff0c;现在参考了一些资料&#xff0c;回过头重新总结下个人对矩阵乘法的理解&#xff08;严格来说是点乘&#xff09;。 理解矩阵和矩阵的乘法&#x…

HTML、CSS表格的斜表头样式设置title 画对角线

我里面有用到layui框架的影响&#xff0c;实际根据你自己的框架来小调下就可以 效果如下 上代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wi…

29. Three.js案例-自定义平面图形

29. Three.js案例-自定义平面图形 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染 3D 场景的核心类。它利用 WebGL 技术在浏览器中渲染 3D 图形。 构造器 THREE.WebGLRenderer(parameters : object) 参数类型描述parametersobject可选参数对象&…

一条线上的点

给你一个数组 points &#xff0c;其中 points[i] [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。 提示&#xff1a; 1 < points.length < 300points[i].length 2-104 < xi, yi < 104points 中的所有点 互不相同 解析&#xff1a;使用斜…

WebRTC服务质量(05)- 重传机制(02) NACK判断丢包

WebRTC服务质量&#xff08;01&#xff09;- Qos概述 WebRTC服务质量&#xff08;02&#xff09;- RTP协议 WebRTC服务质量&#xff08;03&#xff09;- RTCP协议 WebRTC服务质量&#xff08;04&#xff09;- 重传机制&#xff08;01) RTX NACK概述 WebRTC服务质量&#xff08;…

八股—Java基础(二)

目录 一. 面向对象 1. 面向对象和面向过程的区别&#xff1f; 2. 面向对象三大特性 3. Java语言是如何实现多态的&#xff1f; 4. 重载&#xff08;Overload&#xff09;和重写&#xff08;Override&#xff09;的区别是什么&#xff1f; 5. 重载的方法能否根据返回值类…

linux ibus rime 中文输入法,快速设置为:默认简体 (****)

本文环境&#xff1a; ubuntu 22.04 直接 apt install ibus-rime 输入法的安全性&#xff0c;人们应该关注吧&#xff01;&#xff01;&#xff1f;&#xff1f; 云输入法&#xff1f;将用户的输入信息传输到云端吗&#xff1f;恐怕很多人的银行账户和密码&#xff0c;早就上…

uniapp使用百度地图配置了key,但是显示Map key not configured

搞了我两天的一个问题。 hbuilderx版本&#xff1a;4.36 问题介绍&#xff1a; 我的项目是公司的项目&#xff0c;需要在H5端使用百度地图&#xff0c;使用vue-cli创建的uniapp&#xff0c;就是uni代码在src里的目录结构。就是使用这种方式才会遇到这个问题。 问题原因&#xf…

ensp 静态路由配置

A公司有广州总部、重庆分部和深圳分部3个办公地点&#xff0c;各分部与总部之间使用路由器互联。广州、重庆、深圳的路由器分别为R1、R2、R3&#xff0c;为路由器配置静态路由&#xff0c;使所有计算机能够互相访问&#xff0c;实训拓扑图如图所示 绘制拓扑图 给pc机配置ip地址…

3分钟读懂数据分析的流程是什么

数据分析是基于商业目的&#xff0c;有目的地进行收集、整理、加工和分析数据&#xff0c;提炼出有价值的 信息的一个过程。整个过程大致可分为五个阶段&#xff0c;具体如下图所示。 1.明确目的和思路 在开展数据分析之前&#xff0c;我们必须要搞清楚几个问题&#xff0c;比…