系统性全面的讲解软件开发cmake,以及列出详细的步骤和分析和包含内容和要点和案例

系统性全面的讲解软件开发cmake,以及列出详细的步骤和分析和包含内容和要点和案例

下午1:17

main-thumb-pb-3045-200-wkomexlqbcxpravoniszumuijaodcygr.jpeg

GPT-5

下面是一份系统性、全面的 CMake 学习与实战指南,涵盖基础到进阶概念、项目组织、常见模式、跨平台与构建类型、第三方库集成、测试/打包/部署、性能与诊断、最佳实践,以及完整案例。你可以把它当作一个从入门到生产级的路线图。

目录

    1. CMake 是什么与适用场景
    2. 基本概念与术语
    3. 最小工作示例与基本命令
    4. 目录结构与工程组织
    5. 构建类型、生成器与多配置
    6. 目标(target)与可见性(PUBLIC/PRIVATE/INTERFACE)
    7. 变量、缓存与工具链
    8. 查找与集成第三方库(find_package/FetchContent/ExternalProject)
    9. 配置、生成与安装(configure_file/install)
    10. 测试、覆盖率与持续集成(CTest)
    11. 打包与发布(CPack)
    12. 跨平台与跨编译(toolchain/平台差异)
    13. 现代 CMake 最佳实践
    14. 常见问题与排错思路
    15. 完整案例:多目标项目(库+可执行+测试+安装+打包)
  1. CMake 是什么与适用场景
  • 定义:CMake 是跨平台构建系统的元构建工具(build system generator)。它读取 CMakeLists.txt,生成实际构建系统(如 Ninja、Makefiles、Visual Studio、Xcode)。
  • 适用:
    • 跨平台 C/C++ 项目(亦支持 Fortran、CUDA、Objective-C 等)。
    • 多组件、多库工程,复杂依赖、插件化项目。
    • 需要统一编译选项、测试、安装、打包的工程。
  1. 基本概念与术语
  • 源(out-of-source)构建:推荐将构建产物放在独立 build 目录,避免污染源码树。
  • 生成器(Generator):Ninja、Unix Makefiles、Visual Studio、Xcode 等。
  • 目标(Target):add_library/add_executable 创建的构建实体。
  • 属性(Property):与目标、目录、源文件等绑定的元数据,如 INCLUDE_DIRECTORIES、COMPILE_DEFINITIONS、POSITION_INDEPENDENT_CODE。
  • 接口(Usage Requirements):通过 target_link_libraries 的 PUBLIC/PRIVATE/INTERFACE 传播编译选项与包含路径。
  • 包(Package):通过 find_package 查找的已安装库(Config 或 Module 模式)。
  • 预设(Presets):CMakePresets.json 管理构建配置/工具链/缓存变量。
  1. 最小工作示例与基本命令
    最小示例
  • 目录结构
    • CMakeLists.txt
    • src/main.cpp

CMakeLists.txt:

  • cmake_minimum_required(VERSION 3.20)
  • project(hello VERSION 1.0 LANGUAGES CXX)
  • add_executable(hello src/main.cpp)
  • set_target_properties(hello PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES)
  • 构建
    • mkdir -p build && cd build
    • cmake -S .. -B . -G Ninja -DCMAKE_BUILD_TYPE=Release
    • cmake --build . --config Release
    • ./hello

常用命令速览

  • project(name VERSION x.y LANGUAGES C CXX)
  • add_library(name [STATIC|SHARED|OBJECT|INTERFACE] ...) / add_executable(name ...)
  • target_link_libraries(tgt [PRIVATE|PUBLIC|INTERFACE] deps...)
  • target_include_directories(tgt [PRIVATE|PUBLIC|INTERFACE] dirs...)
  • target_compile_features(tgt PUBLIC cxx_std_20)
  • target_compile_definitions(tgt PRIVATE FOO=1)
  • set(var value [CACHE TYPE "doc"])
  • option(ENABLE_X "desc" ON)
  • message(STATUS|WARNING|FATAL_ERROR "text")
  • if()/elseif()/else()/endif()
  • foreach()/while()/function()/macro()
  • file()/string()/list()/math() 工具命令
  • configure_file(in out [@ONLY])
  • install(TARGETS ...)/install(FILES ...)/install(DIRECTORY ...)
  • find_package(Pkg CONFIG REQUIRED)
  • include(GNUInstallDirs)
  • enable_testing()/add_test()/ctest
  • include(FetchContent)/FetchContent_MakeAvailable
  • export()/install(EXPORT ...) 生成可被 find_package(CONFIG) 使用的包
  1. 目录结构与工程组织
    推荐结构(现代 CMake)
  • CMakeLists.txt
  • cmake/ 模块与工具
  • src/ 源码
  • include/ 公共头文件(供外部使用)
  • tests/ 单元测试
  • examples/ 示例程序
  • third_party/ 外部依赖(如使用 FetchContent 可省略)
  • CMakePresets.json 构建预设
  • package/ 打包相关文件(CPack、LICENSE、README)

顶层 CMakeLists.txt 组织

  • cmake_minimum_required
  • project
  • 选项与工具链检测
  • add_subdirectory(src)
  • add_subdirectory(tests) 可通过选项开启
  • 包与安装配置(GNUInstallDirs、version、config)
  1. 构建类型、生成器与多配置
  • 单配置生成器(Ninja、Unix Makefiles):使用 -DCMAKE_BUILD_TYPE=Debug/Release/RelWithDebInfo/MinSizeRel
  • 多配置生成器(Visual Studio、Xcode、Ninja Multi-Config):构建阶段指定 --config Release
  • 通用编译选项:
    • target_compile_options 而不是全局 add_compile_options
    • 针对不同配置:generator expressions $CONFIG:Debug 或 target_compile_definitions(tgt PRIVATE $<$CONFIG:Debug:DEBUG_BUILD>)
  • LTO/IPO:set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) 或 target_link_options
  1. 目标与可见性(核心)
  • PRIVATE:仅影响当前目标
  • PUBLIC:影响目标自身,并传播给链接该目标的下游
  • INTERFACE:不影响当前(接口库没有源码),只作为传播给下游的“使用要求”
  • 示例:
    • target_link_libraries(app PRIVATE mylib) 不传播
    • target_include_directories(mylib PUBLIC include) 下游会继承 include
  • 接口库:
    • add_library(mylib INTERFACE)
    • target_compile_features(mylib INTERFACE cxx_std_20)
  1. 变量、缓存与工具链
  • 普通变量:set(VAR value)
  • 缓存变量:set(VAR value CACHE TYPE "doc") 可通过 -DVAR=value 从命令行设置,并持久化到 CMakeCache.txt
  • 选项:option(ENABLE_SSE "enable sse" ON)
  • 工具链文件:用于交叉编译或指定编译器/SDK
    • cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake
    • 设定 CMAKE_SYSTEM_NAME、CMAKE_C_COMPILER、CMAKE_CXX_COMPILER、CMAKE_SYSROOT 等
  • 预设(CMakePresets.json):
    • 配置 presets: 指定 generator、toolchain、cacheVars
    • 构建 presets: 指定目标、并发、配置
    • 调试 presets: 结合 IDE/调试器
  1. 查找与集成第三方库
  • find_package 的两种模式:
    • Config 模式:寻找 FooConfig.cmake(推荐,现代包通常提供)
    • Module 模式:使用 CMake 自带或自定义 FindFoo.cmake
  • 使用方法:
    • find_package(fmt CONFIG REQUIRED)
    • target_link_libraries(app PRIVATE fmt::fmt)
  • FetchContent(拉取源码并作为子项目构建,适合可复现构建)
    • include(FetchContent)
    • FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 10.2.1)
    • FetchContent_MakeAvailable(fmt)
    • target_link_libraries(app PRIVATE fmt::fmt)
  • ExternalProject_Add(外部独立构建,隔离性更强)
  • pkg-config(find_package(PkgConfig) + pkg_check_modules)
  • 优先级建议:
    • Config 模式(已安装或本地包)
    • FetchContent(锁版本、可复现)
    • Module/FindXXX 自定义
    • ExternalProject(必要时)
  1. 配置、生成与安装
  • 配置头文件:
    • configure_file(config.h.in config.h)
    • 在 config.h.in 使用 #cmakedefine 或 @PROJECT_VERSION@
  • 安装:
    • include(GNUInstallDirs) 获取标准路径(CMAKE_INSTALL_LIBDIR、INCLUDEDIR)
    • install(TARGETS mylib EXPORT mylib-targets
      RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
    • install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
    • install(EXPORT mylib-targets NAMESPACE myproj:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj)
    • 写一个 myprojConfig.cmake 与 myprojConfigVersion.cmake,使外部项目 find_package(myproj CONFIG) 即可
  • 版本文件:
    • include(CMakePackageConfigHelpers)
    • write_basic_package_version_file(...)
  1. 测试、覆盖率与持续集成
  • 启用测试:
    • enable_testing()
    • add_executable(tests tests/main.cpp)
    • add_test(NAME unit COMMAND tests)
  • 与 GoogleTest 集成(推荐 FetchContent)
  • 代码覆盖率:
    • Debug 配置下添加编译选项 -O0 -g --coverage(gcc/clang)
    • 使用 lcov/gcovr 生成报告,可通过 custom target 集成
  • CTest:
    • ctest -j 8 --output-on-failure
    • 结合 CDash 做测试仪表盘
  1. 打包与发布(CPack)
  • include(CPack)
  • 设置基础信息:
    • set(CPACK_PACKAGE_NAME "myproj")
    • set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
    • set(CPACK_GENERATOR "TGZ;ZIP;DEB;RPM")
  • 打包:
    • cpack -G TGZ -C Release
  • 对 DEB/RPM 设置依赖与元数据(CPACK_DEBIAN_PACKAGE_DEPENDS 等)
  1. 跨平台与跨编译
  • 平台差异管理:
    • if(WIN32) if(APPLE) if(UNIX) if(MSVC) if(MINGW) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    • 生成器表达式处理差异:target_compile_definitions(tgt PRIVATE $<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>)
  • Windows:
    • 静态/动态运行库:CMAKE_MSVC_RUNTIME_LIBRARY(MultiThreaded/MD/MTd 等,3.15+)
  • Android/iOS/嵌入式:
    • 使用官方/NDK 工具链文件
  • CUDA、OpenMP、SSE/AVX:
    • enable_language(CUDA) 或 target_compile_options 条件开启
  • Position Independent Code:
    • set(CMAKE_POSITION_INDEPENDENT_CODE ON) 对静态库开启 PIC
  1. 现代 CMake 最佳实践
  • 面向目标而非全局:尽量用 target_* 命令管理编译/链接/包含
  • 避免全局变量污染:使用作用域(function)和目标属性
  • 使用 INTERFACE 目标导出使用需求
  • 尽量用 find_package(CONFIG) + 别名目标(foo::foo)
  • 最小可见性:PRIVATE 默认,必要时 PUBLIC/INTERFACE
  • 生成器表达式代替手写 if 分支
  • 使用预设与工具链锁定环境
  • 用 FetchContent 锁定第三方版本,必要时导出/安装其 targets
  • 对安装与导出保持一致性,提供 Config 文件,便于下游集成
  • 保持 CMake 最低版本合理(例如 3.20+),同时善用新特性(Presets、MSVC 运行库、IPO)
  1. 常见问题与排错思路
  • 头文件找不到:
    • 检查 target_include_directories 作用域
    • 确认消费端是否通过 target_link_libraries 链接产生传播
  • 链接错误/符号未定义:
    • 链接顺序(对旧版链接器)、漏链静态库、或可见性不当
    • 对 SHARED 库在 Windows 可能需要定义导出符号(WINDOWS_EXPORT_ALL_SYMBOLS 或生成导出头)
  • 多配置/单配置混淆:
    • Ninja/Makefiles 需要 CMAKE_BUILD_TYPE,MSVC 需要 --config
  • find_package 找不到:
    • CMAKE_PREFIX_PATH、CMAKE_MODULE_PATH 设置
    • 查找日志:cmake --log-level=VERBOSE 或 message(DEBUG)
  • 交叉编译失败:
    • 工具链文件未设定 CMAKE_SYSTEM_NAME/COMPILER
    • sysroot/SDK 路径
  • 选项从缓存无法更新:
    • 修改后需要清理缓存或使用 -U/--fresh(Presets 支持 clean)
  1. 完整案例:多目标项目(库 + 可执行 + 测试 + 安装 + 打包)
    目录结构
  • CMakeLists.txt
  • include/myproj/myproj.h
  • src/myproj.cpp
  • src/main.cpp
  • tests/test_main.cpp
  • cmake/PreventInSourceBuild.cmake
  • CMakePresets.json
  • package/LICENSE, README.md

示例代码
include/myproj/myproj.h

  • #pragma once
  • #include <string>
  • namespace myproj { std::string greet(std::string name); }

src/myproj.cpp

  • #include "myproj/myproj.h"
  • #include <fmt/core.h>
  • namespace myproj {
    std::string greet(std::string name) { return fmt::format("Hello, {}!", name); }
    }

src/main.cpp

  • #include "myproj/myproj.h"
  • #include <iostream>
  • int main(int argc, char** argv) {
    std::string name = argc > 1 ? argv[1] : "World";
    std::cout << myproj::greet(name) << std::endl;
    return 0;
    }

tests/test_main.cpp

  • #include "myproj/myproj.h"
  • #include <cassert>
  • int main() {
    auto s = myproj::greet("CMake");
    assert(s == "Hello, CMake!");
    return 0;
    }

cmake/PreventInSourceBuild.cmake

  • if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
    message(FATAL_ERROR "In-source build is not allowed. Use a separate build directory.")
    endif()

顶层 CMakeLists.txt

  • cmake_minimum_required(VERSION 3.20)
  • include(cmake/PreventInSourceBuild.cmake)
  • project(myproj VERSION 1.2.3 LANGUAGES CXX)
  • option(MYPROJ_BUILD_TESTS "Build tests" ON)
  • set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
  • include(GNUInstallDirs)
  • FetchContent 获取 fmt
  • include(FetchContent)
  • FetchContent_Declare(fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 10.2.1)
  • FetchContent_MakeAvailable(fmt)
  • add_library(myproj src/myproj.cpp)
  • add_library(myproj::myproj ALIAS myproj)
  • target_include_directories(myproj
    PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
  • target_link_libraries(myproj
    PUBLIC fmt::fmt)
  • target_compile_features(myproj PUBLIC cxx_std_17)
  • add_executable(myproj_cli src/main.cpp)
  • target_link_libraries(myproj_cli PRIVATE myproj::myproj)
  • 安装
  • install(TARGETS myproj myproj_cli
    EXPORT myproj-targets
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
  • install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
  • 导出与包配置
  • include(CMakePackageConfigHelpers)
  • write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/myprojConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion)
  • configure_package_config_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/myprojConfig.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/myprojConfig.cmake"
    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj)
  • install(EXPORT myproj-targets
    NAMESPACE myproj::
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj)
  • install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/myprojConfig.cmake"
    "${CMAKE_CURRENT_BINARY_DIR}/myprojConfigVersion.cmake"
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj)
  • 测试
  • if(MYPROJ_BUILD_TESTS)
    enable_testing()
    add_executable(myproj_tests tests/test_main.cpp)
    target_link_libraries(myproj_tests PRIVATE myproj::myproj)
    add_test(NAME greet_test COMMAND myproj_tests)
    endif()
  • 打包
  • set(CPACK_PACKAGE_NAME "myproj")
  • set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
  • set(CPACK_GENERATOR "TGZ;ZIP")
  • include(CPack)

cmake/myprojConfig.cmake.in

  • @PACKAGE_INIT@
  • include(CMakeFindDependencyMacro)
  • 如需依赖,可使用 find_dependency(fmt CONFIG)
  • include("${CMAKE_CURRENT_LIST_DIR}/myproj-targets.cmake")

CMakePresets.json

  • {
    "version": 5,
    "cmakeMinimumRequired": { "major": 3, "minor": 20 },
    "configurePresets": [
    { "name": "debug", "displayName": "Debug-Ninja", "generator": "Ninja",
    "binaryDir": "build/debug",
    "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "MYPROJ_BUILD_TESTS": true } },
    { "name": "release", "displayName": "Release-Ninja", "generator": "Ninja",
    "binaryDir": "build/release",
    "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "MYPROJ_BUILD_TESTS": false } }
    ],
    "buildPresets": [
    { "name": "debug", "configurePreset": "debug" },
    { "name": "release", "configurePreset": "release" }
    ],
    "testPresets": [
    { "name": "debug", "configurePreset": "debug", "output": { "outputOnFailure": true } }
    ]
    }

构建与运行

  • cmake --preset debug
  • cmake --build --preset debug -j
  • ctest --preset debug
  • cmake --install build/release --prefix /tmp/myproj
  • cpack --config build/release/CPackConfig.cmake -G TGZ

要点总结

  • 优先使用面向目标的“现代 CMake”写法
  • 用 PUBLIC/PRIVATE/INTERFACE 精准传播“使用要求”
  • 使用 FetchContent 或 find_package(CONFIG) 管理第三方依赖
  • 提供安装与导出配置,便于下游项目 find_package 使用
  • 使用 CTest/CPack 打通测试与发布
  • 用 Presets 固化环境,减少“这台机器能编译那台不行”的问题

如果你提供你的具体项目结构或需求(语言标准、目标平台、依赖列表、是否需要安装/打包),我可以给出一份完全贴合的 CMakeLists.txt。

同步定位与地图构建(SLAM)技术为移动机器人或自主载具在未知空间中的导航提供了核心支撑。借助该技术,机器人能够在探索过程中实时构建环境地图并确定自身位置。典型的SLAM流程涵盖传感器数据采集、数据处理、状态估计及地图生成等环节,其核心挑战在于有效处理定位与环境建模中的各类不确定性。 Matlab作为工程计算与数据可视化领域广泛应用的数学软件,具备丰富的内置函数与专用工具箱,尤其适用于算法开发与仿真验证。在SLAM研究方面,Matlab可用于模拟传感器输出、实现定位建图算法,并进行系统性能评估。其仿真环境能显著降低实验成本,加速算法开发与验证周期。 本次“SLAM-基于Matlab的同步定位与建图仿真实践项目”通过Matlab平台完整再现了SLAM的关键流程,包括数据采集、滤波估计、特征提取、数据关联与地图更新等核心模块。该项目不仅呈现了SLAM技术的实际应用场景,更为机器人导航与自主移动领域的研究人员提供了系统的实践参考。 项目涉及的核心技术要点主要包括:传感器模型(如激光雷达与视觉传感器)的建立与应用、特征匹配与数据关联方法、滤波器设计(如扩展卡尔曼滤波与粒子滤波)、图优化框架(如GTSAM与Ceres Solver)以及路径规划与避障策略。通过项目实践,参与者可深入掌握SLAM算法的实现原理,并提升相关算法的设计与调试能力。 该项目同时注重理论向工程实践的转化,为机器人技术领域的学习者提供了宝贵的实操经验。Matlab仿真环境将复杂的技术问题可视化与可操作化,显著降低了学习门槛,提升了学习效率与质量。 实践过程中,学习者将直面SLAM技术在实际应用中遇到的典型问题,包括传感器误差补偿、动态环境下的建图定位挑战以及计算资源优化等。这些问题的解决对推动SLAM技术的产业化应用具有重要价值。 SLAM技术在工业自动化、服务机器人、自动驾驶及无人机等领域的应用前景广阔。掌握该项技术不仅有助于提升个人专业能力,也为相关行业的技术发展提供了重要支撑。随着技术进步与应用场景的持续拓展,SLAM技术的重要性将日益凸显。 本实践项目作为综合性学习资源,为机器人技术领域的专业人员提供了深入研习SLAM技术的实践平台。通过Matlab这一高效工具,参与者能够直观理解SLAM的实现过程,掌握关键算法,并将理论知识系统应用于实际工程问题的解决之中。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
在嵌入式开发领域,配置开发环境是至关重要的一步,尤其是对于基于Arm Cortex-M系列的GD32MCU微控制器。为了帮助您更好地理解操作这一过程,强烈推荐阅读《使用Cmake配置GD32MCU开发环境指南》。 参考资源链接:[使用Cmake配置GD32MCU开发环境指南](https://wenku.youkuaiyun.com/doc/10ytseuumi?spm=1055.2569.3001.10343) 配置GD32MCU开发环境涉及多个步骤,首先,您需要安装交叉编译工具链,如GNU Arm Embedded Toolchain,它允许您在不同的宿主机上为目标硬件编译代码。接下来,安装C/C++ MinGW编译器,这是在Windows平台上进行开发的关键组件,它提供了对多种硬件架构的支持。安装Cmake工具之后,您可以利用它来生成不同编译器的构建文件,从而简化整个配置过程。 安装Vscode集成开发环境及其相关插件,为您的开发工作提供一个强大的代码编辑调试平台。另外,安装OpenOCD调试工具是调试GD32MCU应用程序不可或缺的一部分,它帮助您将固件下载到微控制器并进行调试。 在配置开发环境之后,您需要编写CmakeLists.txt文件,这是Cmake项目的配置核心。通常包括根目录下的CMakeLists.txt文件,它定义了项目的整体结构构建规则,以及特定于GD32MCU的Cortex-M3.cmake配置文件。对于包含库文件的项目,还需要关注gd_libs文件夹中的CMakeLists.txt文件,以及源代码文件夹src中的CMakeLists.txt文件,这些文件共同管理着项目源代码的编译链接。 最后,您可以通过Cmake生成构建系统,使用编译器进行编译,并使用OpenOCD完成下载调试。在这一过程中,您可能需要检查交叉编译器的路径设置是否正确,以及Cmake配置文件是否针对您的硬件软件环境进行了适当的修改。 通过遵循《使用Cmake配置GD32MCU开发环境指南》中的步骤,您将能够搭建起一个功能完备的开发环境,并准备好进行高效的GD32MCU开发工作。为了进一步提升您的开发技能,建议在解决当前问题后继续研究Cmake的高级应用,以及深入探索VscodeOpenOCD的高级调试技巧。 参考资源链接:[使用Cmake配置GD32MCU开发环境指南](https://wenku.youkuaiyun.com/doc/10ytseuumi?spm=1055.2569.3001.10343)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值