%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 头文件引入
头文件扩展名为 `.h`,引入头文件的语法有两种:
| | 编译器查找头文件的路径 |
| ---------------- | ----------------------------------------------------------- |
| `#include <文件名>` | 仅在 "**==系统头文件目录==**" 中查找该头文件(例如 `/usr/include`,为标准库头文件所在路径) |
| `#include "文件名"` | 首先在 **==当前源文件所在的目录==** 中查找,若未找到则再去 **系统头文件目录** 中查找 |
#### 指定额外的头文件搜索路径
![[05-工具/GNU 工具/gcc 编译器#^1fmyfx]]
<br><br>
# 防止头文件重复引入
进行 **防卫式声明**,两种等效方式:
- 方式一:使用**预处理指令** `#ifndef` 进行条件编译
- 方式二:使用**预处理指令** `#pragma once` (gcc、clang、msvc 编译器支持)
###### 方式一
```c++
#ifndef __HEADER_H
#define __HEADER_H
// 头文件内容
...
#endif
```
其中,宏的名称可以随意取。
###### 方式二
使用 `#pragma once` 后,无论这个头文件被包含多少次,**它的内容都只会在每个编译单元中被处理一次**,防止头文件内容被重复包含。
```c++
#pragma once
// 头文件内容
...
```
<br><br><br>
# 防止头文件循环引用
"**头文件循环引用**" 出现在**两个类==互相包含==(其中一方必然以 "==指针==" 形式)** 的情况,两个头文件**互相 `#include` 对方**,导致循环无解。
解决办法:
- 对于 "**持有对方==指针==**" 的一方(如下例的 `class A`):
- 在其**头文件中仅对 `class B` 做==前向声明==**,而在其**源文件(`A.cpp` )中才引入对方头文件 `B.h`**
- (源文件中的函数里会操作 B,故需要 B 的完整定义)
- 对于 "**持有对方==实例==**" 的一方(如下例的 `class B`):
- 在其头文件中**正常引入对方头文件**, `#include "A.h"`
### 示例
```cpp title=A.h
#pragma once
class B; // 仅做前向声明
class A {
public:
B* b; // 无需完整定义
void func();
};
```
```cpp title=A.cpp
#include "A.h"
#include "B.h" // 故引入B.h头文件
void A::func() {
b->func(); // 需要B类型的完整定义
}
```
```cpp title=B.h
#pragma once
#include <iostream>
#include "A.h" // 故引入A的头文件
class B {
public:
A a; // 持有A类实例, 需要A的完整定义
};
```
```cpp title=B.cpp
#include "B.h"
using namespace std;
void B::func() {
cout << "Done" << endl;
}
```
```cpp title=main.cpp
#include "A.h"
#include "B.h"
int main() {
A a;
B b;
a.func();
b.func();
}
```
<br><br><br>
# 分离式编译
分离式编译:
- 对于 "**类**":在头文件(`.h`)中**定义类**,而**在源文件(`.cpp`)中实现其成员函数**;
- 对于 "**模版**" (类模版、函数模版): "**声明**" 和 "**定义/实现**" 在编译时必须位于同一个 "**翻译单元**" 中。
> [!note] 类的辅助函数的放置位置
>
> 围绕着一个类通常包含一些**辅助函数**,如 `add`、`read`、`print` 、`swap` 等,其定义的操作从**概念上来说属于类的接口的组成部分**,但**实际上并不属于类本身**。这些**函数原型的声明**通常应该与**类声明**放在同一个头文件中,从而类的使用者只需要引入一个头文件。而**函数定义则与类成员函数的实现**放在同一个源文件中 。
> [!NOTE] 若某个头文件的内容 **==仅在类的实现文件(.cpp)中使用==**,则应当仅在 `.cpp` 源文件文件中 `#include`,而不要放在头文件中引入,从而**减少编译依赖,提高编译速度**。
<br><br>
## "模版声明" 与 "模版定义" 的分离
> [!important] 模板的 "**声明**" 和 "**定义**" 必须位于同一个翻译单元中
>
> 原因在于:编译器在 **==实例化一个模版==** 时 **==必须要能看到模版的确切定义==**,从而**根据模版定义代入 "模版参数" 完成替换**,**生成实例化代码**(模版类、模版函数)。
>
> 如果**模版的声明与定义分离**,例如**模版声明放在 `.h` 头文件中而不引入定义**,则**对其他引入该 `.h` 头文件的源文件而言,其编译期的 "翻译单元上下文" 中无法找到模版定义,故尝试实例化模版时将导致编译错误**。
>
> Ps:对 C++编译器而言,编译器在**处理各个==翻译单元 TPU== 时生成==符号引用==**,交由 **==链接器==在==链接阶段==解析这些符号引用**。
>
> - 当**调用函数时**,编译器**只需要看到函数的声明**;
> - 当创建类类型的实例对象时,编译器只需看到**类的定义**,而不需要知道类的实现代码。
>
> ^cpj1sg
因此,在处理 "**模版声明**" 与 "**模版定义**" 的处理上有两种方式:
- (1)**模版声明与实现集中**:**两者放置在==同一个头文件==中**。
- 头文件后缀通常命名为 `.hpp` 或 `.hxx`;
- (2)**模版声明与实现分离**: **在头文件内末尾通过 `#include "xxx.tcc"` ==引入模版实现==**;
- **头文件 `.hpp` 中只包含模版声明**:**类模版定义**(对成员函数只有声明),**函数模版声明**;
- **源文件 `.tcc` / `.impl` / `.inl` 中**包含 "**==模版函数的具体定义实现==**","**==内联函数的定义==**"。
示例:
```cpp title=MyTemplate.hpp
// MyTemplate.hpp(声明)
#pragma once
template<typename T>
class MyTemplate {
public:
void print();
};
// include实现的文件
#include "MyTemplate.impl"
```
> [!example] 示例一:STL 头文件中,类模版成员函数大多都直接定义在类定义中。
>
> `libstdc++` 中,头文件 `<vector>` 内部先后引入了 `bits/stl_vector.h` 与 `bits/vector.tcc` 两个文件。
>
> - 前者**包含 `std::vector<>` 类模版的定义**,**一些较高层的成员函数(调用其他底层函数)则在类定义中的直接实现**,例如 `push_back()`。
> - 后者则**对 `emplace_back()`、`reserve()` 等 "最底层" 的模版函数**进行了实现。
>
> ![[_attachment/02-开发笔记/01-cpp/预处理相关/cpp-头文件说明.assets/IMG-cpp-头文件说明-5C80BA3A829AC411B194650B94A5893D.png|794]]
>
>
> [!example] 示例二:
>
> workflow 项目代码中,**类模版的函数定义放入 ==`xxx.inl` 文件==** 中,而在**在头文件中末尾通过 `#include "xxx.inl"` 引入该文件**。
>
> ![[_attachment/02-开发笔记/01-cpp/预处理相关/cpp-头文件说明.assets/IMG-cpp-头文件说明-D1C3E9F2C7E011E69343995830989443.png|595]]
>
>
<br><br><br>
# 头文件使用建议
- 用户自定义头文件**应当使用 `#include "xxx"` 引入**。
- 头文件中**不应该使用任何 using 指令**,避免导致命名空间污染。
### 头文件中不应当使用任何 using 指令
在**头文件**中,**不应当使用任何 using 指令**,包括 using 声明(例如`using std::vector;`)和 using 指示(`using namespact std;`),而是**明确指定命名空间**,如`std::vector<int>` [^1] 。
> [!NOTE] 在源文件中使用 `using` 指令是安全的,因为其影响范围仅仅局限于该源文件,不会影响到其它文件。
###### 原因说明
由于 "**头文件中的内容在预处理阶段会==被拷贝插入到引用该头文件的源文件==中**",
因此 **"头文件中的 `using` 指令" 会被加入到所有 "==引入该头文件的源文件==" 中**,从而可能引起每个源文件的**命名空间污染**,导致**命名冲突**而引发一系列问题。
- (1)可能 **导致名称解析的模糊性**,编译器报错
- 示例一:假设一个源文件**自定义了一个 `count()` 函数**,而**其引入的头文件中使用 `using std::count` 或 `using namespace std` 引入了 `std` 命名空间中的 `count()` 函数**,则将导致**编译器对该源文件报错**——名称解析混淆,编译器无法知道调用的是哪一个函数。
- 示例二:C++11 与 boost 库中都有 `shared_ptr`,不指定 namespace 或者 using 了两个 namespace 可能导致混淆问题,使得编译器无法确定代码中调用的是哪一个函数。
- 示例三:Windows 头文件里自带一种类型 `byte`,而 C++17 中新增了 `std::byte`,如果一个头文件里包含有`using namespace std` ,则当编译器使用 C++17 标准时**将报错名称解析混淆**——编译器就不知道它是 Windows 的 `byte` 还是 `std: byte` 省略了 `std` 。
- (2)**代码可读性下降**:对于源代码中的**一个与 "`std` 命名空间中名称" 同名的名称**,无法该名称是来自 `std` 还是其他地方,特别是当与其他库交互时。
> [!quote]
> 
<br><br>
### 引入头文件时的先后顺序
为了避免潜在的编译问题,推荐的 `#include` 顺序:
1. 先包含**对应的头文件**(对于 `.cpp` 源文件)。
2. 再包含 C++ 标准库头文件。
3. 接着包含第三方库头文件(如 Boost, OpenCV)。
4. 最后包含项目内部的其他头文件。
示例:
```cpp
// MyClass.cpp
#include "MyClass.h" // 先包含自己的头文件
#include <iostream> // C++ 标准库
#include <vector>
#include "OtherClass.h" // 其他项目头文件
```

<br><br>
### 头文件中应当编写的内容
头文件中通常放置 "**只能被定义一次的实体**",例如:
- 函数相关:
- 函数原型,即函数声明;
- **内联函数定义**
- 内联函数的特殊规则要求在每个使用它的文件中都进行定义。
- 确保内联定义对多文件中各个文件可用,最简便的方法是:**将内联定义放在定义类的头文件中**。
- **static 函数定义**(内部链接性)
- 变量/常量相关:
- **`#define` 定义的符号常量**
- **`const` 、`constexpr` 定义的全局常量**(内部链接性)
- **`static` 全局常量**(内部链接性)
- `extern` 变量声明( `extern` 仅作声明,**定义和初始化赋值放到头文件对应的 `.cpp` 文件中进行**)
- 用户自定义类型的 "**声明**":类类型、结构体、联合体、枚举类型的声明
- **==模版声明==**
- 模版定义通过**在头文件中使用 `#include "..."` 引入**
> [!caution] 头文件中禁止定义具有 "**==外部链接性==**" 的变量!
>
> 否则当头文件被其它多个源文件包含时,将出现 "**重定义错误**"。
> 正确用法: 在头文件中用 `extern` 声明外部变量,而在头文件对应的 `.cpp` 源文件中进行定义和赋值。
> [!NOTE] `static` 修饰的全局变量、函数具有 "**内部链接性**"。
>
> 因此多个源文件引入后不会导致重定义错误,而是**每个翻译单元一份独立实例**。
>
> ![[_attachment/02-开发笔记/01-cpp/预处理相关/cpp-头文件说明.assets/IMG-cpp-头文件说明-14B3F960FAA3E9A13E5B2577B2AC5BA6.png|377]]
>
>
>
> [!NOTE] 文件组织说明
>
> - 头文件:包含结构声明、**使用/操纵这些结构的函数原型**、类声明、模版声明等
> - 源代码文件:与头文件中的声明对应的具体实现,包含与结构有关的函数的代码,例如函数**定义**等。
> - 源代码文件:包含**调用与结构相关的函数**的代码;
<br><br>
# 参考资料
# Footnotes
[^1]: 《C++ Primer》P75、P704