CMake 实例详解

CMake 实例详解

在这里插入图片描述
CMake 是开源、跨平台的构建工具,可以让我们通过编写简单的配置文件去生成本地的 Makefile,这个配置文件是独立于运行平台和编译器的,这样就不用亲自去编写 Makefile 了,而且配置文件可以直接拿到其它平台上使用,无需修改,非常方便,从而做到 “Write once, run everywhere”。

CMakeLists.txt 是 CMake 的配置文件。notepad++ 支持 CMake 相关的语法,并提供自动提示,推荐使用它来编写 CMakeLists.txt。
在这里插入图片描述

  1. 编写 CMake 配置文件 CMakeLists.txt
  2. 执行命令cmake PATH生成 Makefile,PATH 是 CMakeLists.txt 所在的目录
  3. 使用make命令进行编译

Windows 版本的 CMake

CMake 下载

选择最新版本的 cmake-3.16.2-win64-x64.msi (msi 是需要安装的,zip 是免安装的)
https://cmake.org/download/
安装注意:在出现 “安装选项框中” 选中第三个,把 CMake 添加到当前用户的系统环境变量上
在这里插入图片描述

MinGW 下载

MinGW (Minimalist GNU for Windows),是一种 GCC 编译环境的安装程序
网盘下载,链接: https://pan.baidu.com/s/1dkO31D7klnBVJN8XOJ30sQ 提取码: m9dh
安装界面里勾选 mingw32-gcc-g+±bin 安装这一项即可
在这里插入图片描述
安装完成后,把 gcc.exe 所在的路径(如 C:\MinGW\bin)添加到 Windows 系统环境变量上

编译运行

写好 CMakeLists.txt 和 源文件
启动 Windows 命令终端,并进入到源码路径,执行以下命令
cmake -G"MinGW Makefiles" .
mingw32-make
.\demo_test.exe
Hello World
Hello CMake

Linux 版本的 CMake

本文主要讲述在 Linux 下如何使用 CMake 来编译我们的程序

安装CMake

sudo apt install cmake
查看版本
cmake -version

简单示例

单个源文件

准备好源文件,如 main.cpp

#include <stdio.h>
int main(int argc, char *argv[])
{
    printf("Hello World\n");
    printf("Hello CMake\n");
    return 0;
}

编写 CMakeLists.txt,跟 main.cpp 同一目录
CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的
符号 # 后面的内容被认为是注释
命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔

cmake_minimum_required (VERSION 2.8)
project (demo1)
add_executable (demo main.cpp)

对于上面的 CMakeLists.txt 文件,依次出现了几个命令:

  1. cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本
  2. project:参数值是 demo1,该命令表示项目的名称是 demo1
  3. add_executable:将名为 main.cpp 的源文件编译成一个名称为 demo 的可执行文件

编译项目
在当前目录执行cmake .,成功生成了 Makefile,还有 cmake 运行时自动生成的文件
然后再使用make命令编译得到 demo 可执行文件
在这里插入图片描述

同一目录,多个源文件

新建一个 add.cpp,添加一函数

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

main.cpp

#include <stdio.h>
#include "add.h"
int main(int argc, char *argv[])
{
    int sum = 0;
    sum = add(10, 20);
    printf("sum = %d\n", sum);
    return 0;
}

目录文件形式如下
02demo/
├── add.cpp
├── add.h
└── main.cpp

方法一

这时候,CMakeLists.txt 可以改成如下的形式,只需在 add_executable 里面添加 add.cpp 就可

cmake_minimum_required (VERSION 2.8)
project (demo2)
add_executable (demo main.cpp add.cpp)

这样写当然没什么问题,但是如果源文件很多,把所有源文件的名字都加进去将是一件烦人的工作

方法二

更省事的方法是使用aux_source_directory命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。其语法如下:
aux_source_directory(<dir> <variable>)

cmake_minimum_required (VERSION 2.8)
project (demo2)
aux_source_directory (./ DIR_SRCS)
add_executable (demo ${DIR_SRCS})

这样,CMake 会将当前目录所有源文件的文件名赋值给变量DIR_SRCS,再指定变量DIR_SRCS中的源文件编译成一个名称为 demo 的可执行文件

方法三

aux_source_directory也存在弊端,它会把指定目录下的所有源文件都加进来,实际项目中可能有些是我们不需要的文件,此时我们可以使用set命令去新建变量来存放需要的源文件,如下:

cmake_minimum_required (VERSION 2.8)
project (demo2)
set ( DIR_SRCS
	  ./main.cpp
	  ./add.cpp )
add_executable (demo ${DIR_SRCS})
多个目录,多个源文件

一般来说,当文件比较多时,我们会进行分类管理,根据功能把代码放在不同目录下,这样方便查找
现在进一步将 add.h 和 add.cpp 文件移动到 add 目录下,新建 sub.h 和 sub.cpp 放到 sub 目录下
目录文件形式如下
03demo/
├── add
│ ├── add.cpp
│ └── add.h
├── sub
│ ├── sub.cpp
│ └── sub.h
└── main.cpp

main.cpp

#include <stdio.h>
#include "add.h"
#include "sub.h"
int main(int argc, char *argv[])
{
    int sum = 0;
    int diff = 0;
    sum = add(10, 20);
    printf("sum = %d\n", sum);
    diff = sub(100, 30);
    printf("diff = %d\n", diff);
    return 0;
}
方法一

CMakeLists.txt 和 main.cpp 在同一目录下,内容修改成如下所示

cmake_minimum_required (VERSION 2.8)
project (demo3)
include_directories (./add ./sub)
aux_source_directory (./ DIR_SRCS)
aux_source_directory (./add DIR_SRCS1)
aux_source_directory (./sub DIR_SRCS2)
add_executable (demo ${DIR_SRCS} ${DIR_SRCS1} ${DIR_SRCS2})

该文件添加了下面的内容:
第 3 行,include_directories是用来向工程添加多个指定头文件的搜索路径,路径之间用空格分隔
第 5/6 行,因为源文件分布在 2 个目录下,所以我们多使用了 2 次aux_source_directory

方法二(lib 库)

对于这种情况,也可以分别在 add 和 sub 目录里各编写一个 CMakeLists.txt 文件
为了方便,我们可以先将 add 和 sub 目录里的文件分别编译成 lib 库再由 main 函数调用
根目录中的 CMakeLists.txt

cmake_minimum_required (VERSION 2.8)
project (demo3)
include_directories (./add ./sub)
add_subdirectory (./add)
add_subdirectory (./sub)
aux_source_directory (./ DIR_SRCS)
add_executable (demo ${DIR_SRCS})
target_link_libraries (demo myadd mysub)

该文件添加了下面的内容:
第 4/5 行,add_subdirectory指明本项目包含子目录 add 和 sub,这样当执行 cmake 时,就会进入子目录去找 CMakeLists.txt 来生成 Makefile
第 8 行,target_link_libraries指明可执行文件 demo 需要链接一个名为 myadd 和 mysub 的链接库
这种写法默认是使用动态库,如果目录下只有静态库,这种写法就会去链接静态库

add 目录中的 CMakeLists.txt

aux_source_directory (./ DIR_LIB_SRCS)
add_library (myadd SHARED ${DIR_LIB_SRCS})

sub 目录中的 CMakeLists.txt

aux_source_directory (./ DIR_LIB_SRCS)
add_library (mysub STATIC ${DIR_LIB_SRCS})

在该文件中使用命令add_library将 add 目录的源文件编译成动态库,将 sub 目录的源文件编译成静态库
add_library第 1 个参数指定库的名字;第 2 个参数决定是动态还是静态,不写默认静态;第 3 个参数指定生成库的源文件。注意:SHARED 和 STATIC 是 cmake 的关键字,必须大写!

常用的组织结构

一般来说,我们习惯按如下方式来摆放文件,让我们把前面的文件重新组织下:
把源文件放到 src 目录下
把头文件放到 include 目录下
把生成的库文件放到 lib 目录下
把生成的对象文件放到 build 目录下
把最终输出的 elf 文件放到 bin 目录下
04demo/
├── bin
├── build
├── include
│ ├── add.h
│ └── sub.h
├── lib
└── src
├── lib_add
│ └── add.cpp
├── sub
│ └── sub.cpp
└── main.cpp
这里我们设定将 add.cpp 编译成静态库和动态库,sub.cpp 则与 main.cpp 一起编译

在最外层新建一个 CMakeLists.txt,最外层的 CMakeLists.txt 用于掌控全局,使用add_subdirectory来添加要生成 elf 文件的源码目录即可,内容如下

cmake_minimum_required (VERSION 2.8)
project (demo4)
add_subdirectory (./src)

src 目录下,新建一个 CMakeLists.txt,内容如下

add_subdirectory (./lib_add)
aux_source_directory (./    DIR_SRCS1)
aux_source_directory (./sub DIR_SRCS2)
include_directories (../include)
link_directories (${PROJECT_SOURCE_DIR}/lib)
add_executable (demo ${DIR_SRCS1} ${DIR_SRCS2})
target_link_libraries (demo myadd)
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

第 1 行,要先包含 lib_add 这个目录,因为需要依赖生成的库文件
第 5 行,link_directories添加非标准库的搜索路径
第 8 行,EXECUTABLE_OUTPUT_PATHPROJECT_SOURCE_DIR是 cmake 自带的预定义变量
EXECUTABLE_OUTPUT_PATH:目标二进制可执行文件的存放位置
PROJECT_SOURCE_DIR:当前工程的根目录
这里set的意思是把存放 elf 文件的位置设置为工程根目录下的 bin 目录

lib_add 目录下,也要新建一个 CMakeLists.txt,内容如下

aux_source_directory (./ DIR_LIB_SRCS)
add_library (myadd_shared SHARED ${DIR_LIB_SRCS})
add_library (myadd_static STATIC ${DIR_LIB_SRCS})
set_target_properties (myadd_shared PROPERTIES OUTPUT_NAME "myadd")
set_target_properties (myadd_static PROPERTIES OUTPUT_NAME "myadd")
set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

第 4/5 行,set_target_properties设置输出的名称
第 6 行,LIBRARY_OUTPUT_PATH也是 cmake 自带的预定义变量
LIBRARY_OUTPUT_PATH:库文件的默认输出路径
这里set的意思是把存放库文件的位置设置为工程目录下的 lib 目录

PS:上面使用set_target_properties重新定义了库的输出名字,如果不用set_target_properties也可以,那么库的名字就是add_library里定义的名字,只是我们连续 2 次使用add_library指定库名字时,这个名字不能相同,而set_target_properties可以把名字设置为相同,只是最终生成的库文件后缀不同,这样相对来说会好一点

下面来运行 cmake,不过这次先让我们切到 build 目录下,运行cmake ..
此时 Makefile 会在 build 目录下生成,然后在 build 目录下运行make
这时我们再看 bin 目录,已经生成了 demo 可执行文件,lib 目录也生成了 libmyadd.a 和 libmyadd.so
在这里插入图片描述

这里解释一下为什么要在 build 目录下运行 cmake?从前面几个 case 中可以看到,如果不这样做,cmake 运行时生成的附带文件就会跟源码文件混在一起,这样会对程序的目录结构造成污染,而在 build 目录下运行 cmake,生成的附带文件就只会待在 build 目录下,如果我们不想要这些文件就可以直接清空 build 目录,非常方便

添加编译选项

有时编译程序时想添加一些编译选项,如 -Wall、-std=c++11 等,就可以使用add_compile_options来操作,也可以通过set命令修改CMAKE_CXX_FLAGSCMAKE_C_FLAGS,这两个是 cmake 自带的预定义变量,用于设置编译选项
这两种方式的效果是一样的,但请注意它们还是有区别的:
add_compile_options命令添加的编译选项是针对所有编译器的(包括 c 和 c++ 编译器),而set命令设置CMAKE_C_FLAGSCMAKE_CXX_FLAGS变量则是分别只针对 c 和 c++ 编译器的
整体目录结构如下
05demo/
├── bin
├── build
├── CMakeLists.txt
└── main.cpp
这里以一个简单程序来做演示,main.cpp 如下

#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
    auto sum = 100;
    cout << "sum = " << sum << endl;
    return 0;
}

CMakeLists.txt 内容如下

cmake_minimum_required (VERSION 2.8)
project (demo5)
#add_compile_options (-std=c++11 -Wall)
set (CMAKE_CXX_FLAGS "-std=c++11 -Wall ${CMAKE_CXX_FLAGS}")
aux_source_directory (./ DIR_SRCS)
add_executable (demo ${DIR_SRCS})
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

cd 到 build 目录下,执行cmake .. && make命令,就可以在 bin 目录下得到 elf 文件

添加控制选项

有时我们希望在编译代码时只编译一些指定的源码,这时可以使用 cmake 的option命令
假设我们现在的工程会生成 2 个 bin 文件,main1 和 main2,现在整体结构如下
06demo/
├── bin
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
├── main1.cpp
└── main2.cpp

外层的 CMakeLists.txt 内容如下

cmake_minimum_required (VERSION 2.8)
project (demo6)
option (MYDEBUG "enable debug mode" OFF)
add_subdirectory (./src)

这里使用了option命令,其第一个参数是这个 option 的名字,第二个参数是字符串,用来描述这个 option 是来干嘛的,第三个是 option 的值,ONOFF,也可以不写,不写就是默认 OFF

src 目录下的 CMakeLists.txt,如下

add_executable (main1 main1.cpp)
if (MYDEBUG)
    add_executable (main2 main2.cpp)
else()
    message (STATUS "Currently is not in debug mode")
endif()
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

这里使用了 if-else 根据 option 来决定是否编译 main2.cpp
message为用户打印显示一条消息,可以用下述可选的关键字指定消息类型
(无) = 重要消息
STATUS = 非重要消息
WARNING = CMake 警告,会继续执行
AUTHOR_WARNING = CMake 警告 (dev),会继续执行
SEND_ERROR = CMake 错误,继续执行,但是会跳过生成的步骤
FATAL_ERROR = CMake 错误,终止所有处理过程

其中 main1.cpp 和 main2.cpp 的内容如下

#include <stdio.h>
int main(int argc, char *argv[])
{
    printf("hello, this is main1\n");
    return 0;
}

#include <stdio.h>
int main(int argc, char *argv[])
{
    printf("hello, this is main2\n");
    return 0;
}

cd 到 build 目录下输入cmake .. && make就可以只编译出 main1,如果想编译出 main2

  1. 直接修改 CMakeLists.txt,把 OFF 改成 ON,这种方法有点麻烦
  2. cd 到 build 目录,然后输入cmake .. -DMYDEBUG=ON && make,这样就可以编译出 main1 和 main2
支持 GDB

让 CMake 支持 gdb 的设置也很简单,只需要指定Debug模式下开启-g选项,加入 3 行代码就可以
set (CMAKE_BUILD_TYPE "Debug")
set (CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set (CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

如下文件目录结构
07demo/
├── bin
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp

外层 CMakeLists.txt 还是老样子

cmake_minimum_required (VERSION 2.8)
project (demo7)
add_subdirectory (./src)

src 目录下的 CMakeLists.txt 则加入 gdb 的设置,如下

aux_source_directory (./ DIR_SRCS)
set (CMAKE_BUILD_TYPE "Debug")
set (CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set (CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
add_executable (demo ${DIR_SRCS})
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

main 函数

#include <stdio.h>
int main(int argc, char *argv[])
{
    int i = 10;
    i += 20;
    printf("hello, this is gdb test: i = %d\n", i);
    return 0;
}

cd 到 build 目录下输入cmake .. && make就可以编译出 demo,然后用命令gdb demo来设置断点,验证一下

配置交叉编译器

CMake 在 ubuntu 系统下默认使用系统的 gcc、g++ 编译器,对于我们做嵌入式开发的,免不了需要配置交叉编译工具链,来看一下 CMake 怎么配置交叉工具链。下面以 arm-hisiv600-linux-gcc 为例说明一下

如下文件目录结构
08demo/
├── bin
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp

外层 CMakeLists.txt 在一开始加入相关设置

cmake_minimum_required (VERSION 2.8)
# 添加配置
set (CMAKE_SYSTEM_NAME Linux)
set (CMAKE_C_COMPILER   "arm-hisiv600-linux-gcc")
set (CMAKE_CXX_COMPILER "arm-hisiv600-linux-g++")
set (CMAKE_FIND_ROOT_PATH /opt/hisi-linux/x86-arm/arm-hisiv600-linux/target)
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# 完成配置
project (demo8)
add_subdirectory (./src)

第 3 行,告知当前使用的是交叉编译方式,必须配置
第 4 行,指定 C 交叉编译器,必须配置
第 5 行,指定 C++ 交叉编译器,必须配置
第 6 行,指定交叉编译环境安装目录,非必须配置
第 7 行,从来不在指定目录下查找工具程序,非必须配置
第 8 行,只在指定目录下查找库文件,非必须配置
第 9 行,只在指定目录下查找头文件,非必须配置

src 目录下的 CMakeLists.txt 还是老样子,如下

aux_source_directory (./ DIR_SRCS)
add_executable (demo ${DIR_SRCS})
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

main 函数

#include <stdio.h>
int main(int argc, char *argv[])
{
    printf("hello, cross-compiler test\n");
    return 0;
}

cd 到 build 目录下输入cmake .. && make就可以编译出 demo,然后拿到板子上跑一下,验证一下

执行 shell 命令

可以通过execute_process调用一条或多条 shell 命令,这是在执行cmake命令就会执行其中的 shell 命令
execute_process(COMMAND <shell命令> WORKING_DIRECTORY <这条shell命令执行的工作目录>)

cmake_minimum_required (VERSION 2.8)
project (demo9)
execute_process (
  COMMAND mkdir xxx 
  COMMAND touch xxx/123.txt
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)

如果要在执行make阶段,去执行一些 shell 命令,可以用add_custom_targetadd_custom_command
这对于要在构建一个目标之前或之后执行一些操作非常有用,比如,我们的工程需要用到 log4cpp 的库,而我们拿到的只是一个 log4cpp-1.1.tar.gz 压缩包,所以需要用 shell 命令去解压并且编译它

cmake_minimum_required (VERSION 2.8)
project (demo9)
#增加一个没有输出的目标,并指定了ALL选项,使得它总是被构建
add_custom_target (
  MyTarget ALL
  COMMAND echo "Creating target MyTarget..."
  DEPENDS hello.txt
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  COMMENT "This is add_custom_target"
)
#增加一个客制命令用来产生一个输出
add_custom_command (
  OUTPUT hello.txt
  COMMAND touch hello.txt
  COMMAND echo "Generating hello.txt file..."
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  COMMENT "This is add_custom_command"
)
#为某个目标(如库或可执行程序)添加一个客制命令
add_custom_command (
  TARGET MyTarget
  PRE_BUILD
  COMMAND echo "Executing a fake command..."
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  COMMENT "This command will be executed before building target MyTarget"
)

第 7 行:表示 MyTarget 依赖于 hello.txt
第 21 行:标记为在什么时候执行命令:编译前(PRE_BUILD),编译后(POST_BUILD),链接前(PRE_LINK)
第 9/17/25 行:COMMENT在构建的时候,该值会被当成信息在执行该命令之前显示

这类似于 Makefile 如下的定义

all:MyTarget
    ......
MyTarget:hello.txt
    touch hello.txt
    echo "Generating hello.txt file..."

执行make时,打印如下
在这里插入图片描述

PS:上面的第 20~26 行这一段可以引申扩展一下,比如我编译完某个 lib 库之后,想把它拷贝到别的目录下,就可以使用POST_BUILD来自定义命令,如

aux_source_directory (./ DIR_LIB_SRCS)
add_library (myadd SHARED ${DIR_LIB_SRCS})
add_custom_command (
  TARGET myadd
  POST_BUILD
  COMMAND cp -rf libmyadd.so /tmp
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)

总结

以上是对 CMake 的一点学习记录,通过简单的例子让大家入门 CMake,CMake的知识点还有很多,更多高级用法可以在网上搜索。总之,CMake 可以让我们不用去编写复杂的 Makefile,并且跨平台,是个非常强大并值得一学的工具

参考资料

https://blog.csdn.net/whahu1989/article/details/82078563
https://blog.csdn.net/zhuiyunzhugang/article/details/88142908
https://blog.csdn.net/weixin_36926794/article/details/80297929
https://blog.csdn.net/gubenpeiyuan/article/details/51096777
https://blog.csdn.net/bytxl/article/details/50634868

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
CMake是一款跨平台的开源构建工具,用于管理和构建软件项目。CMake手册详解CMake官方提供的教程文档,旨在帮助开发者深入了解和学习CMake的使用方法和功能。 CMake手册详解下载可以通过多种方式实现。首先,可以访问CMake官方网站,从中文或英文文档页面下载最新的CMake手册详解。同时,CMake还提供了在线文档,可以直接在网页上查看和学习。 此外,CMake的源代码仓库中也包含了完整的CMake手册详解,可以通过克隆或下载源代码仓库的方式获取。在源代码仓库中,手册详解通常在docs目录下,按照不同版本和语言进行组织和分类。 另一种常见的获取CMake手册详解的方式是通过社区和论坛。CMake拥有广泛的用户社区,许多开发者在论坛中分享他们编写的教程和手册详解。可以通过搜索引擎或加入相关社区来获取这些用户贡献的手册详解,并进行下载和学习。 无论通过哪种方式获取CMake手册详解,建议选择最新版本的手册以获取最新的特性和功能说明。通过仔细阅读手册详解,开发者可以系统性地学习CMake的用法,并运用CMake来管理和构建自己的软件项目。手册中还包含了各种示例和案例,帮助开发者更好地理解和应用CMake的各个方面。 总之,CMake手册详解是学习和使用CMake的重要参考资料,通过下载和阅读手册详解,开发者可以更好地掌握CMake的功能和用法,加快软件项目的构建和管理速度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cfl927096306

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

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

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

打赏作者

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

抵扣说明:

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

余额充值