CMake简介

news2024/11/15 17:46:06

文章目录

  • 为什么需要头文件
    • 为什么 C++ 需要声明
    • 头文件 - 批量插入几行代码的硬核方式
    • 头文件进阶 - 递归地使用头文件
  • CMake
    • 什么是编译器
    • 多文件编译与链接
    • CMake 的命令行调用
    • 为什么需要库(library)
    • CMake 中的静态库与动态库
    • CMake 中的子模块
    • 子模块的头文件如何处理
    • 目标的一些其他选项
    • 第三方库 - 作为纯头文件引入
    • 第三方库 - 作为子模块引入

为什么需要头文件

为什么 C++ 需要声明

在多文件编译章中,说到了需要在 main.cpp 声明 hello() 才能引用。为什么?

因为需要知道函数的参数和返回值类型:这样才能支持重载,隐式类型转换等特性。例如 show(3),如果声明了 void show(float x),那么编译器知道把 3 转换成 3.0f 才能调用。
让编译器知道 hello 这个名字是一个函数,不是一个变量或者类的名字:这样当我写下 hello() 的时候,他知道我是想调用 hello 这个函数,而不是创建一个叫 hello 的类的对象。
其实,C++ 是一种强烈依赖上下文信息的编程语言,举个例子:

vector < MyClass > a;   // 声明一个由 MyClass 组成的数组

如果编译器不知道 vector 是个模板类,那他完全可以把 vector 看做一个变量名,把 < 解释为小于号,从而理解成判断‘vector’这个变量的值是否小于‘MyClass’这个变量的值。
正因如此,我们常常可以在 C++ 代码中看见这样的写法:typename decay::type
因为 T 是不确定的,导致编译器无法确定 decay 的 type 是一个类型,还是一个值。因此用 typename 修饰来让编译器确信这是一个类型名……
在这里插入图片描述
为了使用 hello 这个函数,我们刚才在 main.cpp 里声明了 void hello() 。
但是如果另一个文件 other.cpp 也需要用 hello 这个函数呢?也在里面声明一遍?
如果能够只写一遍,然后自动插入到需要用 hello 的那些 .cpp 里就好了……在这里插入图片描述

头文件 - 批量插入几行代码的硬核方式

没错,C 语言的前辈们也想到了,他们说,既然每个 .cpp 文件的这个部分是一模一样的,不如我把 hello() 的声明放到单独一个文件 hello.h 里,然后在需要用到 hello() 这个声明的地方,打上一个记号,#include “hello.h” 。然后用一个小程序,自动在编译前把引号内的文件名 hello.h 的内容插入到记号所在的位置,这样不就只用编辑 hello.h 一次了嘛~
后来,这个编译前替换的步骤逐渐变成编译器的了一部分,称为预处理阶段,#define 定义的宏也是这个阶段处理的。
此外,在实现的文件 hello.cpp 中导入声明的文件 hello.h 是个好习惯,可以保证当 hello.cpp 被修改时,比如改成 hello(int),编译器能够发现 hello.h 声明的 hello() 和定义的 hello(int) 不一样,避免“沉默的错误”。在这里插入图片描述
实际上 cstdio 也无非是提供了 printf 等一系列函数声明的头文件而已,实际的实现是在 libc.so 这个动态库里。其中 这种形式表示不要在当前目录下搜索,只在系统目录里搜索,”hello.h” 这种形式则优先搜索当前目录下有没有这个文件,找不到再搜索系统目录。
此外,在实现的文件 hello.cpp 中也导入声明的文件 hello.h 是个好习惯:
可以保证当 hello.cpp 被修改时,比如改成 hello(int),编译器能够发现 hello.h 声明的 hello() 和定义的 hello(int) 不一样,避免“沉默的错误”(虽然对支持重载的 C++ 不奏效)
可以让 hello.cpp 中的函数需要相互引用时,不需要关心定义的顺序。

头文件进阶 - 递归地使用头文件

在 C++ 中常常用到很多的类,和函数一样,类的声明也会被放到头文件中。
有时候我们的函数声明需要使用到某些类,就需要用到声明了该类的头文件,像这样递归地 #include 即可:在这里插入图片描述
但是这样造成一个问题,就是如果多个头文件都引用了 MyClass.h,那么 MyClass 会被重复定义两遍:在这里插入图片描述
解决方案:在头文件前面加上一行:#pragma once
这样当预处理器第二次读到同一个文件时,就会自动跳过
通常头文件都不想被重复导入,因此建议在每个头文件前加上这句话
在这里插入图片描述

CMake

什么是编译器

编译器,是一个根据源代码生成机器码的程序。

 >g++ main.cpp -o a.out

该命令会调用编译器程序g++,让他读取main.cpp中的字符串(称为源码),并根据C++标准生成相应的机器指令码,输出到a.out这个文件中,(称为可执行文件)。

> ./a.out

之后执行该命令,操作系统会读取刚刚生成的可执行文件,从而执行其中编译成机器码,调用系统提供的printf函数,并在终端显示出Hello, world。
在这里插入图片描述

多文件编译与链接

单文件编译虽然方便,但也有如下缺点:
所有的代码都堆在一起,不利于模块化和理解。
工程变大时,编译时间变得很长,改动一个地方就得全部重新编译。
因此,我们提出多文件编译的概念,文件之间通过符号声明相互引用。

> g++ -c hello.cpp -o hello.o
> g++ -c main.cpp -o main.o

其中使用 -c 选项指定生成临时的对象文件 main.o,之后再根据一系列对象文件进行链接,得到最终的a.out:

> g++ hello.o main.o -o a.out

在这里插入图片描述

CMake 的命令行调用

读取当前目录的 CMakeLists.txt,并在 build 文件夹下生成 build/Makefile:

> cmake -B build

让 make 读取 build/Makefile,并开始构建 a.out:

> make -C build

以下命令和上一个等价,但更跨平台:

> cmake --build build

执行生成的 a.out:

> build/a.out

在这里插入图片描述

为什么需要库(library)

在这里插入图片描述
有时候我们会有多个可执行文件,他们之间用到的某些功能是相同的,我们想把这些共用的功能做成一个库,方便大家一起共享。
库中的函数可以被可执行文件调用,也可以被其他库文件调用。
库文件又分为静态库文件和动态库文件。
其中静态库相当于直接把代码插入到生成的可执行文件中,会导致体积变大,但是只需要一个文件即可运行。
而动态库则只在生成的可执行文件中生成“插桩”函数,当可执行文件被加载时会读取指定目录中的.dll文件,加载到内存中空闲的位置,并且替换相应的“插桩”指向的地址为加载后的地址,这个过程称为重定向。这样以后函数被调用就会跳转到动态加载的地址去。
Windows:可执行文件同目录,其次是环境变量%PATH%
Linux:ELF格式可执行文件的RPATH,其次是/usr/lib等


CMake 中的静态库与动态库

CMake 除了 add_executable 可以生成可执行文件外,还可以通过 add_library 生成库文件。
add_library 的语法与 add_executable 大致相同,除了他需要指定是动态库还是静态库:

add_library(test STATIC source1.cpp source2.cpp)  # 生成静态库 libtest.a
add_library(test SHARED source1.cpp source2.cpp)  # 生成动态库 libtest.so

动态库有很多坑,特别是 Windows 环境下,初学者自己创建库时,建议使用静态库。
但是他人提供的库,大多是作为动态库的, 之后会讨论如何使用他人的库。
创建库以后,要在某个可执行文件中使用该库,只需要:
target_link_libraries(myexec PUBLIC test) # 为 myexec 链接刚刚制作的库 libtest.a
其中 PUBLIC 的含义稍后会说明(CMake 中有很多这样的大写修饰符)
在这里插入图片描述

CMake 中的子模块

复杂的工程中,我们需要划分子模块,通常一个库一个目录,比如:
在这里插入图片描述

这里我们把 hellolib 库的东西移到 hellolib 文件夹下了,里面的 CMakeLists.txt 定义了 hellolib 的生成规则。
要在根目录使用他,可以用 CMake 的 add_subdirectory 添加子目录,子目录也包含一个 CMakeLists.txt,其中定义的库在 add_subdirectory 之后就可以在外面使用。
子目录的 CMakeLists.txt 里路径名(比如 hello.cpp)都是相对路径,这也是很方便的一点。

在这里插入图片描述

子模块的头文件如何处理

因为 hello.h 被移到了 hellolib 子文件夹里,因此 main.cpp 里也要改成:
在这里插入图片描述

如果要避免修改代码,我们可以通过 target_include_directories 指定
a.out 的头文件搜索目录:(其中第一个 hellolib 是库名,第二个是目录)
在这里插入图片描述

这样甚至可以用 <hello.h> 来引用这个头文件了,因为通过 target_include_directories 指定的路径会被视为与系统路径等价:
在这里插入图片描述
但是这样如果另一个 b.out 也需要用 hellolib 这个库,难道也得再指定一遍搜索路径吗?
不需要,其实我们只需要定义 hellolib 的头文件搜索路径,引用他的可执行文件 CMake 会自动添加这个路径:

在这里插入图片描述

这里用了 . 表示当前路径,因为子目录里的路径是相对路径,类似还有 … 表示上一层目录。
此外,如果不希望让引用 hellolib 的可执行文件自动添加这个路径,把 PUBLIC 改成 PRIVATE 即可。这就是他们的用途:决定一个属性要不要在被 link 的时候传播。

目标的一些其他选项

除了头文件搜索目录以外,还有这些选项,PUBLIC 和 PRIVATE 对他们同理:

target_include_directories(myapp PUBLIC /usr/include/eigen3)  # 添加头文件搜索目录
target_link_libraries(myapp PUBLIC hellolib)                  # 添加要链接的库
target_add_definitions(myapp PUBLIC MY_MACRO=1)               # 添加一个宏定义
target_add_definitions(myapp PUBLIC -DMY_MACRO=1)             # 与 MY_MACRO=1 等价
target_compile_options(myapp PUBLIC -fopenmp)                 # 添加编译器命令行选项
target_sources(myapp PUBLIC hello.cpp other.cpp)              # 添加要编译的源文件

以及可以通过下列指令(不推荐使用),把选项加到所有接下来的目标去:

include_directories(/opt/cuda/include)     # 添加头文件搜索目录
link_directories(/opt/cuda)                # 添加库文件的搜索路径
add_definitions(MY_MACRO=1)                # 添加一个宏定义
add_compile_options(-fopenmp)              # 添加编译器命令行选项

第三方库 - 作为纯头文件引入

有时候我们不满足于 C++ 标准库的功能,难免会用到一些第三方库。
最友好的一类库莫过于纯头文件库了,这里是一些好用的 header-only 库:

nothings/stb - 大名鼎鼎的 stb_image 系列,涵盖图像,声音,字体等,只需单头文件!
Neargye/magic_enum - 枚举类型的反射,如枚举转字符串等(实现方式很巧妙)
g-truc/glm - 模仿 GLSL 语法的数学矢量/矩阵库(附带一些常用函数,随机数生成等)
Tencent/rapidjson - 单纯的 JSON 库,甚至没依赖 STL(可定制性高,工程美学经典)
ericniebler/range-v3 - C++20 ranges 库就是受到他启发(完全是头文件组成)
fmtlib/fmt - 格式化库,提供 std::format 的替代品(需要 -DFMT_HEADER_ONLY)
gabime/spdlog - 能适配控制台,安卓等多后端的日志库(和 fmt 冲突!)

只需要把他们的 include 目录或头文件下载下来,然后 include_directories(spdlog/include) 即可。
缺点:函数直接实现在头文件里,没有提前编译,从而需要重复编译同样内容,编译时间长。

第三方库 - 作为子模块引入

第二友好的方式则是作为 CMake 子模块引入,也就是通过 add_subdirectory。
方法就是把那个项目(以fmt为例)的源码放到你工程的根目录:
这些库能够很好地支持作为子模块引入:

fmtlib/fmt - 格式化库,提供 std::format 的替代品
gabime/spdlog - 能适配控制台,安卓等多后端的日志库
ericniebler/range-v3 - C++20 ranges 库就是受到他启发
g-truc/glm - 模仿 GLSL 语法的数学矢量/矩阵库
abseil/abseil-cpp - 旨在补充标准库没有的常用功能
bombela/backward-cpp - 实现了 C++ 的堆栈回溯便于调试
google/googletest - 谷歌单元测试框架
google/benchmark - 谷歌性能评估框架
glfw/glfw - OpenGL 窗口和上下文管理
libigl/libigl - 各种图形学算法大合集

在这里插入图片描述

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

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

相关文章

进入NetApp FAS存储系统loader的三种方法

有时候需要在loader模式下对系统硬件做一些offline的诊断&#xff0c;但offline 对系统物理部件做诊断需要进入到loader模式&#xff0c;如何从一个正常运行的系统进入到loader模式呢&#xff1f; 第一种就是启动的时候看到CtrlC的提示&#xff0c;就可以顺利进入loader。 如…

网络工程毕设-----基于华为ensp搭建校园网

本实验用华为模拟器ensp搭建简单的校园网络&#xff0c;其中用到的技术有动态路由协议OSPF&#xff0c;静态路由配置&#xff0c;HTTP、DNS以及FTP服务器的配置&#xff0c;PNAT端口地址转换协议&#xff0c;MSTP多生成树协议&#xff0c;VLAN划分及配置IP地址划分及配置等! 选…

机器学习实战:Python基于EM期望最大化进行参数估计(十五)

文章目录 1. 前言1.1 EM的介绍1.2 EM的应用场景 2. 高斯混合模型估计2.1 导入函数2.2 创建数据2.3 初始化2.4 Expectation Step2.5 Maximization step2.6 循环迭代可视化 3. 多维情况4. 讨论 1. 前言 1.1 EM的介绍 &#xff08;Expectation-Maximization&#xff0c;EM&#…

实战案例:使用 Python 机器学习预测外卖送餐时间

现在的天气是一天比一天热&#xff0c;好多人周末休息在家的时候&#xff0c;就会选择点外卖&#xff0c;毕竟出去一趟又晒又热。 如果你太饿了&#xff0c;点餐太晚了&#xff0c;就可能去关注外卖员送餐到哪了&#xff0c;还有多少时间能送达。 这些信息在美团、饿了吗的Ap…

MapReduce原理剖析

一、基本介绍 MapReduce是Hadoop的核心&#xff0c;是Google提出的一个软件架构&#xff0c;用于大规模数据集&#xff08;大于1TB&#xff09;的并行运算。概念“Map&#xff08;映射&#xff09;”和“Reduce&#xff08;化简&#xff09;”&#xff0c;及他们的主要思想&am…

AWS 推出开源 AutoML 工具包“AutoGluon”

亚马逊网络服务最近推出了一个开源库&#xff0c;使开发人员只需几行代码即可在图像、文本或表格数据上实现深度学习模型。 AutoGluon 旨在成为一个易于使用且易于扩展的 AutoML 工具包&#xff0c;适合机器学习初学者和专家。它只需几行即可对深度学习模型进行原型设计;自动超…

stm8_独立看门狗配置顺序错误导致不断复位

1、问题 在配置stm8独立看门狗的时候&#xff0c;先设置分频、重载寄存器&#xff0c;然后启动看门狗&#xff0c;发现不断复位。 按照手册中的表格&#xff0c;看门狗的超时时间应该是1s&#xff0c;但是在这1s中多次喂狗也不断复位&#xff0c;然后排查到是配置顺序的问题&…

重新审视MHA与Transformer

本文将基于PyTorch源码重新审视MultiheadAttention与Transformer。事实上&#xff0c;早在一年前博主就已经分别介绍了两者&#xff1a;各种注意力机制的PyTorch实现、从零开始手写一个Transformer&#xff0c;但当时的实现大部分是基于d2l教程的&#xff0c;这次将基于PyTorch…

【实践篇】最全的【DDD领域建模】小白学习手册(文末附资料) | 京东云技术团队

导读 DDD领域建模被各个大小厂商提起并应用&#xff0c;而每个人都有自己的理解&#xff0c;本文就是针对小白&#xff0c;系统地讲解DDD到底是什么&#xff0c;解决了什么问题&#xff0c;及一些建议和实践。本文主要是思想的一种碰撞和分享&#xff0c;希望能对朋友们有所启…

第四章 No.2单点线段树的介绍与使用

文章目录 基本操作练习题1275. 最大数245. 你能回答这些问题吗246. 区间最大公约数 基本操作 单点线段树一共4个常用操作&#xff0c;pushup, build, modify, query 相比区间线段树少了pushdown&#xff0c;懒标记&#xff0c;由于pushdown的实现极容易SF&#xff0c;所以能用…

Python GUI应用程序开发之wxPython库详解

概要 wxPython是一个强大的跨平台GUI工具包&#xff0c;它使用Python编程语言开发&#xff0c;提供了丰富的控件功能。如果你是一名Python开发者&#xff0c;而且希望创建一个功能齐全的桌面应用程序&#xff0c;那么wxPython是一个值得考虑的选择。wxPython是wxWidgets C库的P…

算法——十大排序 (部分未完结)

总结 为什么需要稳定排序&#xff1f; ▪ 让第⼀个关键字的排序结果服务于第⼆个关键字排序中数值相同的那些数 ▪ 主要是为了第⼀次考试分数相同时候&#xff0c;可以按照第⼆次分数的⾼低进行排序 一、冒泡排序 从最简单的冒泡排序开始 思想&#xff1a;交换相邻的元素&am…

电子文件管理系统的最佳实践指南分享

电子文件管理系统是一种专门用于管理电子文件的软件工具&#xff0c;可以帮助组织更有效地管理、存储、检索和共享文件。 首先&#xff0c;在选择适合自己组织的电子文件管理系统时&#xff0c;需要考虑以下几个关键因素。首先&#xff0c;系统的易用性和用户界面是否友好&…

Qt应用开发(基础篇)——布局管理Layout Management

目录 一、前言 二&#xff1a;相关类 三、水平、垂直、网格和表单布局 四、尺寸策略 一、前言 在实际项目开发中&#xff0c;经常需要使用到布局&#xff0c;让控件自动排列&#xff0c;不仅节省控件还易于管控。Qt布局系统提供了一种简单而强大的方式来自动布局小部件中的…

前段时间面试了一些人,有这些槽点跟大家说说

大家好&#xff0c;我是拭心。 前段时间组里有岗位招人&#xff0c;花了些时间面试&#xff0c;趁着周末把过程中的感悟和槽点总结成文和大家讲讲。 简历书写和自我介绍 今年的竞争很激烈&#xff1a;找工作的人数量比去年多、平均质量比去年高。裸辞的慎重&#xff0c;要做好…

Android 第三方库CalendarView

Android 第三方库CalendarView 根据需求和库的使用方式&#xff0c;自己弄了一个合适自己的日历&#xff0c;仅记录下&#xff0c;方便下次弄其他样式的日历。地址 需求&#xff1a; 只显示当月的数据 默认的月视图有矩形的线 选中的天数也要有选中的矩形框 今天的item需要…

强推!大语言模型『百宝书』,一文缕清所有大模型!

夕小瑶科技说 原创 作者 | 王思若 最近&#xff0c;大型语言模型无疑是AI社区关注的焦点&#xff0c;各大科技公司和研究机构发布的大模型如同过江之鲫&#xff0c;层出不穷又眼花缭乱。 让笔者恍惚间似乎又回到了2020年国内大模型“军备竞赛”的元年&#xff0c;不过那时候…

package-lock.json 作用

参照&#xff1a; https://www.cnblogs.com/honkerzh/p/16767566.html

【雕爷学编程】MicroPython动手做(25)——语音合成与语音识别

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

山西电力市场日前价格预测【2023-08-01】

日前价格预测 预测明日&#xff08;2023-08-01&#xff09;山西电力市场全天平均日前电价为310.15元/MWh。其中&#xff0c;最高日前电价为335.18元/MWh&#xff0c;预计出现在19: 45。最低日前电价为288.85元/MWh&#xff0c;预计出现在14: 00。 价差方向预测 1&#xff1a;实…