项目构建优化:CMake

CMake 介绍

在 Linux 工程管理中,make 是常用的工程管理器,但它依赖的 Makefile 语法晦涩、编写复杂,尤其在多文件、跨平台项目中维护成本极高。为解决这一问题,主流方案有两种:

  • 使用 automake 生成 configure 脚本,再由脚本自动生成 Makefile(适用于传统 Linux 项目);
  • 使用 CMake 直接生成 Makefile(跨平台、简单高效,是当前主流选择)。

CMake 是一款 跨平台免费开源的构建工具,核心作用是:通过开发者编写的简单配置文件 CMakeLists.txt,自动生成适配不同平台(Linux、Windows、macOS)、不同编译器(GCC、Clang、MSVC)的 Makefile 或工程文件(如 Visual Studio 项目),彻底摆脱手动编写 Makefile 的麻烦。

image

CMake、Makefile、编译器的关系如下:

image

CMake 安装

Ubuntu 系统安装

Ubuntu 下通过包管理器安装,命令简单且自动配置环境:

# 安装 CMake
sudo apt update  # 更新软件源
sudo apt install cmake -y

# 验证安装(查看版本,确认安装成功)
cmake --version  # 输出示例:cmake version 3.22.1

# 升级 CMake(如需更新到最新版本)
sudo apt upgrade cmake -y

CMake 基础实战

入门操作(单文件编译)

以一个简单的 C 语言程序 main.c 为例,演示 CMake 最基础的使用流程。

步骤 1:准备源码

创建 main.c,内容如下:

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

步骤 2:编写 CMakeLists.txt

main.c 同一目录下,创建 严格命名为 CMakeLists.txt 的文件(注意:文件名首字母大写、Lists 为复数,CMake 仅识别此名称,不可修改),内容如下:

# 设定 CMake 最低版本要求(避免版本兼容问题,推荐 3.10+)
cmake_minimum_required(VERSION 3.10)

# 设定项目名称(可自定义,后续可用 ${PROJECT_NAME} 引用)
project(HelloCMake LANGUAGES C)  # LANGUAGES C 表示仅用 C 语言

# 生成可执行程序
# 语法:add_executable(可执行程序名 依赖的源文件)
# 作用:指定最终生成的可执行文件名为 "hello",依赖源文件 "main.c"
add_executable(hello main.c)

步骤 3:生成 Makefile 并编译

在源码目录执行以下命令:

# 读取 CMakeLists.txt,生成 Makefile(. 表示当前目录)
cmake .

# 执行 make 编译(自动读取生成的 Makefile)
make

# 执行生成的可执行程序
./hello  # 输出:Hello CMake!

执行后目录结构如下(新增的 MakefileCMakeFiles 等为 CMake 生成的文件):

.
├── CMakeFiles/       # CMake 构建过程的临时文件
├── CMakeCache.txt    # CMake 缓存文件(加速后续构建)
├── cmake_install.cmake  # 安装配置文件
├── Makefile          # 自动生成的 Makefile
├── main.c            # 源码文件
└── hello             # 编译生成的可执行程序

源码隔离(推荐实战方式)

入门操作中,CMake 生成的文件与源码混在一起,不利于项目整洁。源码隔离 是工业界常用方式:创建单独的 build 目录存放所有构建文件,避免污染源码。

步骤 1:创建 build 目录并进入

# 在源码目录创建 build 目录(存放构建文件)
mkdir build

# 进入 build 目录(后续所有构建操作在此目录执行)
cd build

步骤 2:生成 Makefile 并编译

# 读取上层目录(.. 表示 build 的父目录,即源码目录)的 CMakeLists.txt,生成 Makefile
cmake ..

# 编译(生成的可执行程序在 build 目录下)
make

# 执行程序(在 build 目录下)
./hello  # 输出:Hello CMake!

执行后目录结构如下(所有构建文件均在 build 内,源码目录仅保留 main.cCMakeLists.txt):

.
├── build/            # 构建目录(所有临时文件、可执行程序在此)
│   ├── CMakeFiles/
│   ├── CMakeCache.txt
│   ├── cmake_install.cmake
│   ├── Makefile
│   └── hello         # 可执行程序
├── main.c            # 源码文件
└── CMakeLists.txt    # CMake 配置文件

优势:如需清理构建文件,直接删除 build 目录即可,不影响源码。

CMake 核心语法与进阶用法

file () 核心用法

核心语法

file(<子命令> [参数]...)  # 子命令大小写敏感,路径用 / 跨平台

高频子命令(C 项目常用)

  • 文件查找(收集 C 源文件)

    GLOB:当前目录(非递归)

    # 收集 src 目录下所有 .c/.h(不包含子目录)
    file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/src/*.c ${PROJECT_SOURCE_DIR}/src/*.h)
    

    GLOB_RECURSE:递归子目录(含 CONFIGURE_DEPENDS)

    # 递归收集 src 及所有子目录的 .c/.h,自动更新文件列表
    file(GLOB_RECURSE ALL_SRC
      CONFIGURE_DEPENDS  # CMake≥3.12,文件变化无需手动 cmake ..
      ${PROJECT_SOURCE_DIR}/src/**/*.c
      ${PROJECT_SOURCE_DIR}/src/**/*.h
    )
    add_executable(my_c_app ${ALL_SRC})  # 生成C可执行文件
    
  • 文件读写(生成配置 / 版本文件)

    # 读取版本文件(content: 1.0.0)
    file(READ ${PROJECT_SOURCE_DIR}/version.txt VER)
    # 生成配置头文件(供C代码包含)
    file(WRITE ${CMAKE_BINARY_DIR}/config.h "#define VERSION \"${VER}\"")
    
  • 复制 / 安装(资源 / 部署)

    # 构建时复制资源到编译目录(C项目配置文件、固件等)
    file(COPY ${PROJECT_SOURCE_DIR}/res 
      DESTINATION ${CMAKE_BINARY_DIR}
      PATTERN "*.tmp" EXCLUDE  # 排除临时文件
    )
    
    # 安装时复制可执行文件到系统(make install)
    file(INSTALL ${CMAKE_BINARY_DIR}/my_c_app
      DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
      PERMISSIONS 755  # 执行权限
    )
    
  • 目录 / 文件操作

    # 创建输出目录(类似 mkdir -p)
    file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib)
    
    # 删除临时文件/目录
    file(REMOVE ${CMAKE_BINARY_DIR}/*.log)  # 删除文件
    file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/temp)  # 递归删目录
    
  • 路径处理(CMake≥3.20)

    set(C_FILE ${PROJECT_SOURCE_DIR}/src/core/func.c)
    file(PATH_GET DIRECTORY DIR ${C_FILE})  # 提取目录:src/core
    file(PATH_GET FILENAME NAME ${C_FILE})  # 提取文件名:func.c
    

C 项目常用组合示例

cmake_minimum_required(VERSION 3.12)
project(my_c_project C)  # 指定C项目

# 递归收集所有C源文件
file(GLOB_RECURSE ALL_SRC
  CONFIGURE_DEPENDS
  ${PROJECT_SOURCE_DIR}/src/**/*.c
  ${PROJECT_SOURCE_DIR}/src/**/*.h
)

# 生成可执行文件
add_executable(my_c_app ${ALL_SRC})

# 复制资源
file(COPY ${PROJECT_SOURCE_DIR}/res DESTINATION ${CMAKE_BINARY_DIR})

# 安装
file(INSTALL ${CMAKE_BINARY_DIR}/my_c_app DESTINATION /usr/local/bin PERMISSIONS 755)

总结

C 项目用 file() 核心就 3 件事:

  • GLOB_RECURSE+CONFIGURE_DEPENDS 收集 .c/.h
  • COPY/INSTALL 处理资源和部署;
  • MAKE_DIRECTORY/REMOVE 管理目录文件。

多文件编译

当项目包含多个源文件(如 main.ca.cb.c)时,需在 add_executable 中指定所有源文件。

示例:多文件项目结构

.
├── build/
├── main.c  # 主函数文件
├── a.c     # 模块 1:实现加法
├── b.c     # 模块 2:实现减法
└── CMakeLists.txt

源文件内容

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

// b.c
int sub(int a, int b) {
    return a - b;
}

// main.c
#include <stdio.h>
// 声明 a.c、b.c 中的函数
int add(int a, int b);
int sub(int a, int b);

int main() {
    printf("3 + 2 = %d\n", add(3, 2));
    printf("3 - 2 = %d\n", sub(3, 2));
    return 0;
}

CMakeLists.txt 配置

cmake_minimum_required(VERSION 3.10)
project(MultiFile LANGUAGES C)

# 多文件编译:指定可执行程序 "calc" 依赖的所有源文件
add_executable(calc main.c a.c b.c)

编译与执行

cd build
cmake ..
make
./calc  # 输出:3 + 2 = 5;3 - 2 = 1

优化:自动收集源文件(多文件场景推荐)

若源文件数量多,手动写所有文件名繁琐,可使用 aux_source_directory 自动收集指定目录下的所有源文件:

cmake_minimum_required(VERSION 3.10)
project(MultiFile LANGUAGES C)

# 语法:aux_source_directory(目录 变量名)
# 作用:将当前目录(.)下所有 .c 文件路径存入变量 SRC_FILES
aux_source_directory(. SRC_FILES) # aux 是 auxiliary(中文意为 “辅助的”)

# 用变量指定源文件(避免手动写多个文件名)
add_executable(calc ${SRC_FILES})

指定头文件路径

当项目包含单独的头文件目录(如 inc/)时,需用 include_directories 告诉编译器头文件位置,否则会报 “头文件未找到” 错误。

示例:带头文件的项目结构

.
├── build/
├── inc/              # 头文件目录
│   └── math.h        # 头文件:声明 add、sub 函数
├── src/              # 源文件目录
│   ├── main.c
│   ├── a.c
│   └── b.c
└── CMakeLists.txt    # 顶层 CMakeLists.txt

头文件与源文件内容

// inc/math.h
#ifndef MATH_H
#define MATH_H
// 声明函数
int add(int a, int b);
int sub(int a, int b);
#endif
// src/a.c
// 引用头文件(后续会用 CMake 简化路径)
#include "../inc/math.h"  
int add(int a, int b) {
    return a + b;
}

// src/main.c
#include <stdio.h>
// 简化引用(需 CMake 指定头文件路径)
#include "math.h"  
int main() {
    printf("3 + 2 = %d\n", add(3, 2));
    return 0;
}

CMakeLists.txt 配置

cmake_minimum_required(VERSION 3.10)
project(WithHeader LANGUAGES C)

# 语法:include_directories(头文件目录1 目录2 ...)
# 作用:告诉编译器,头文件在 "../inc" 目录(相对路径,基于当前 CMakeLists.txt 所在目录)
# 推荐用内置变量 ${CMAKE_CURRENT_SOURCE_DIR} 表示当前 CMakeLists.txt 所在目录,避免硬编码
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)

# 收集 src 目录下的所有源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)

# 生成可执行程序
add_executable(calc ${SRC_FILES})

关键说明

  • CMAKE_CURRENT_SOURCE_DIR 是 CMake 内置变量,指向当前 CMakeLists.txt 所在的目录,跨平台兼容;
  • 若用绝对路径(如 /home/user/project/inc),会导致项目在其他电脑上无法运行,优先用相对路径 + 内置变量。

设定变量与调用 Shell 命令

设定变量

set 命令定义变量,变量引用用 ${变量名},常用于统一管理路径、源文件列表等,提升配置文件的可维护性。

cmake_minimum_required(VERSION 3.10)
project(UseVariable LANGUAGES C)

# 设定头文件目录变量
set(HEADER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/inc)

# 设定源文件目录变量
set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)

# 收集源文件(用变量指定目录)
aux_source_directory(${SRC_DIR} SRC_FILES)

# 使用变量指定头文件路径
include_directories(${HEADER_DIR})

# 使用变量生成可执行程序
add_executable(calc ${SRC_FILES})

调用 Shell 命令

CMake 中用 execute_process 调用 Shell 命令(如 lspwd),需指定命令和输出变量(可选)。

cmake_minimum_required(VERSION 3.10)
project(CallShell LANGUAGES C)

# 语法:execute_process(COMMAND 命令1 [命令2 ...] OUTPUT_VARIABLE 输出变量)
# 作用:执行 "pwd" 命令,将输出(当前路径)存入变量 CURRENT_PATH
execute_process(COMMAND pwd OUTPUT_VARIABLE CURRENT_PATH)

# 打印变量值(用于调试)
message(STATUS "当前目录路径:${CURRENT_PATH}")

# 后续配置...
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_executable(calc ${SRC_FILES})

执行 cmake .. 时会输出:

- 当前目录路径:/home/user/project/build
-- Configuring done
-- Generating done

添加工程子目录与编译库文件

当项目包含子模块(如 lib/ 目录,需编译成库文件)时,用 add_subdirectory 引入子目录,用 add_library 编译库文件(动态库或静态库)。

示例:带子目录库的项目结构

.
├── build/
├── inc/
│   └── math.h        # 头文件:声明 add、sub 函数
├── lib/              # 子目录:存放库的源文件
│   ├── a.c           # 库源文件1:实现 add
│   ├── b.c           # 库源文件2:实现 sub
│   └── CMakeLists.txt# 子目录的 CMake 配置文件
├── src/
│   └── main.c        # 主程序:调用库中的函数
└── CMakeLists.txt    # 顶层 CMake 配置文件

子目录 lib/ 的 CMakeLists.txt

# lib/CMakeLists.txt
# 编译库文件:将 a.c、b.c 编译成动态库或静态库

# 语法:add_library(库名 库类型 源文件1 源文件2 ...)
# 库类型:SHARED(动态库,Linux 下生成 libmath.so)、STATIC(静态库,Linux 下生成 libmath.a)
# 作用1:编译动态库 libmath.so
add_library(math SHARED a.c b.c)

# 作用2:(可选)编译静态库 libmath.a(若需同时生成两种库,需改库名,如 math_static)
# add_library(math_static STATIC a.c b.c)

顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(WithLibrary LANGUAGES C)

# 指定头文件路径(inc/ 目录)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)

# 引入子目录 lib/(CMake 会自动执行 lib/CMakeLists.txt)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib)

# 收集主程序源文件(src/ 目录)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)

# 生成主程序可执行文件
add_executable(calc ${SRC_FILES})

# 链接库文件:主程序 calc 依赖 libmath.so
# 语法:target_link_libraries(可执行程序名 库名1 库名2 ...)
target_link_libraries(calc math)

编译与验证

cd build
cmake ..
make

编译后,build/lib/ 目录下会生成动态库 libmath.sobuild/ 目录下生成主程序 calc,执行 ./calc 即可调用库中的函数。

指明库路径与链接外部库

若主程序依赖的库文件不在 CMake 自动搜索路径(如 /usr/lib、子目录库)中(如外部库 /tmp/libxxx.so),需用 link_directories 指定库的搜索路径,再用 target_link_libraries 链接。

示例:链接外部库

假设主程序依赖 /tmp/liblog.so(外部动态库),CMakeLists.txt 配置如下:

cmake_minimum_required(VERSION 3.10)
project(LinkExternalLib LANGUAGES C)

# 指定头文件路径(若外部库有头文件,需添加)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)

# 指定外部库的搜索路径(/tmp 目录下存放 liblog.so)
# 语法:link_directories(库路径1 路径2 ...)
link_directories(/tmp)

# 收集主程序源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)

# 生成主程序
add_executable(calc ${SRC_FILES})

# 链接外部库:calc 依赖 liblog.so(库名写 "log",CMake 自动补全 "lib" 和 ".so")
target_link_libraries(calc log)

注意link_directories 需在 add_executable 之前,确保 CMake 生成 Makefile 时能找到库路径。

指定工具链(交叉编译)

在嵌入式开发中,需为目标平台(如 ARM 架构的开发板)指定 交叉编译器(如 aarch64-linux-gnu-gcc),CMake 通过设置 CMAKE_C_COMPILERCMAKE_CXX_COMPILER 实现。

示例:为 ARM 架构指定交叉工具链

cmake_minimum_required(VERSION 3.10)
project(CrossCompile LANGUAGES C)

# 指定交叉编译器(根据实际工具链路径修改)
set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc")   # C 交叉编译器
set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++") # C++ 交叉编译器

# 后续配置(头文件、源文件、链接库等,与之前一致)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_executable(calc ${SRC_FILES})
target_link_libraries(calc math)

进阶:工具链文件(多平台切换)

若项目需部署到多个平台(如 x86、ARM、Rockchip),可将工具链配置单独放在 .cmake 文件中,编译时通过 -DCMAKE_TOOLCHAIN_FILE 指定,无需修改顶层 CMakeLists.txt。

步骤 1:ARM 平台工具链文件 arm.toolchain.cmake

# arm.toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)          # 目标系统:Linux
set(CMAKE_SYSTEM_PROCESSOR arm)       # 目标架构:ARM

# 指定交叉编译器路径
set(TOOLCHAIN_DIR "/usr/local/arm-toolchain")
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/arm-linux-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/arm-linux-g++)

# 指定目标平台的头文件和库路径(交叉编译时需用到)
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_DIR})

步骤 2:编译时指定工具链文件

cd build
# 语法:cmake .. -DCMAKE_TOOLCHAIN_FILE=工具链文件路径
cmake .. -DCMAKE_TOOLCHAIN_FILE=${CMAKE_CURRENT_SOURCE_DIR}/arm.toolchain.cmake
make

生成的 calc 可执行程序将适配 ARM 架构,可放到开发板上运行。

指定编译选项

通过 CMAKE_C_FLAGS(C 语言)或 CMAKE_CXX_FLAGS(C++)设置编译选项,如优化等级、警告开关、运行时库路径等。

常用编译选项说明

选项作用
-O2优化等级 2(平衡性能与编译速度,推荐)
-Wall显示所有警告信息
-Werror将警告视为错误(强制修正所有警告)
-Wl,-rpath=./lib指定程序运行时的库搜索路径(./lib)

示例:设置编译选项

cmake_minimum_required(VERSION 3.10)
project(CompileOptions LANGUAGES C)

# 设置 C 语言编译选项:优化等级 O2 + 显示所有警告 + 运行时库路径
set(CMAKE_C_FLAGS "-O2 -Wall -Wl,-rpath=./lib")

# 后续配置
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_executable(calc ${SRC_FILES})
target_link_libraries(calc math)

设定 CMake 最低版本

cmake_minimum_required(VERSION x.x) 设定 CMake 最低版本,避免因版本差异导致的语法不兼容问题(如新版本的 target_include_directories 在旧版本中不可用)。

# 设定最低版本为 3.10(低于此版本的 CMake 会报错)
cmake_minimum_required(VERSION 3.10)

# 若需兼容旧版本,可指定版本范围(如 3.10 到 3.30)
# cmake_minimum_required(VERSION 3.10...3.30)

执行 cmake .. 时,若当前 CMake 版本低于设定值,会报错:

CMake Error at CMakeLists.txt:1 (cmake_minimum_required):
CMake 3.10 or higher is required.  You are running version 3.5.1

设定项目名称与版本

project 命令设定项目名称、版本号和支持的语言,便于版本管理和后续引用。

# 语法:project(项目名 VERSION 主版本.次版本.修订版 LANGUAGES 语言列表)
project(MyProject 
    VERSION 1.2.3        # 版本号:1.2.3(主版本1,次版本2,修订版3)
    LANGUAGES C CXX      # 支持 C 和 C++ 语言
)

# 引用项目相关的内置变量(后续可用于生成版本信息)
message(STATUS "项目名称:${PROJECT_NAME}")       # 输出:MyProject
message(STATUS "项目版本:${PROJECT_VERSION}")    # 输出:1.2.3
message(STATUS "主版本号:${PROJECT_VERSION_MAJOR}") # 输出:1

CMake 常用内置变量

CMake 提供大量内置变量,用于获取项目路径、配置输出目录等,提升配置文件的灵活性和跨平台兼容性。

内置变量含义说明
PROJECT_SOURCE_DIR项目根目录(顶层 CMakeLists.txt 所在的目录)
PROJECT_BINARY_DIR构建目录(执行 cmake 命令的目录,通常是 build 目录)
PROJECT_NAME项目名称(由 project 命令设定)
PROJECT_VERSION项目版本号(由 project(VERSION ...) 设定)
CMAKE_CURRENT_SOURCE_DIR当前处理的 CMakeLists.txt 所在的目录
CMAKE_CURRENT_BINARY_DIR当前目标(可执行程序 / 库)的编译目录(如 build/lib
EXECUTABLE_OUTPUT_PATH自定义可执行程序的输出路径(如 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
LIBRARY_OUTPUT_PATH自定义库文件的输出路径(如 set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
CMAKE_C_COMPILER当前使用的 C 编译器路径(如 /usr/bin/gcc
CMAKE_SYSTEM_NAME目标系统名称(如 Linux、Windows、Darwin(macOS))

实用技巧

精准指定目标的头文件路径(推荐)

include_directories 会为所有目标(可执行程序、库)添加头文件路径,若需为特定目标添加,用 target_include_directories(更精准,避免冲突)。

cmake_minimum_required(VERSION 3.10)
project(TargetHeader LANGUAGES C)

# 生成库文件 math
add_library(math SHARED lib/a.c lib/b.c)

# 只为库 math 添加头文件路径(其他目标不生效)
target_include_directories(math 
    PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc  # PRIVATE:仅库内部使用
)

# 生成主程序 calc
add_executable(calc src/main.c)

# 只为主程序 calc 添加头文件路径,并链接 math 库
target_include_directories(calc 
    PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc   # PUBLIC:calc 和依赖 calc 的目标都可用
)
target_link_libraries(calc math)

清理构建文件

源码隔离场景下,直接删除 build 目录即可清理所有构建文件:

# 在源码目录执行
rm -rf build/

非隔离场景下,用 make clean 清理编译生成的可执行程序和目标文件:

make clean

生成 Visual Studio 项目(Windows 平台)

CMake 支持生成 Windows 下的 Visual Studio 项目文件,只需在执行 cmake 时指定生成器:

# 生成 Visual Studio 2022 项目(64 位)
cmake .. -G "Visual Studio 17 2022" -A x64

执行后会生成 .sln 解决方案文件,用 Visual Studio 打开即可编译。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值