源文件包含

来自cppreference.com
 
 
C++ 语言
 
 

将其他源文件包含到当前源文件中紧随指令之后的一行。

语法

#include < h字符序列 > 换行 (1)
#include " q字符序列 " 换行 (2)
#include 记号序列 换行 (3)
__has_include ( " q字符序列 " )
__has_include ( < h字符序列 > )
(4) (C++17 起)
__has_include ( 字符串字面量 )
__has_include ( < h记号序列 > )
(5) (C++17 起)
1) 搜索由 h字符序列 唯一识别的头,并将该指令替换为这个头的全部内容。
2) 搜索由 q字符序列 识别的源文件,并将该指令替换为这个源文件的全部内容。可以退回至语法 (1) 并将 q字符序列 视为头标识符。
3) 如果 (1) 和 (2) 都不匹配,记号序列会经历宏替换。该指令在替换后会再次尝试匹配 (1) 和 (2)。
4) 检查一个头或源文件是否可以被包含。
5) 如果 (4) 不匹配,h记号序列会经历宏替换。T该指令在替换后会再次尝试匹配 (4)。
换行 - 换行符
h字符序列 - 一个或多个 h字符,并且其中以下内容的出现受条件性支持:
  • 字符 '
  • 字符 "
  • 字符 \
  • 字符序列 //
  • 字符序列 /*
h字符 - 源字符集 (C++23 前)翻译字符集 (C++23 起) 除了换行符和 > 以外的任何成员
q字符序列 - 一个或多个 q字符,并且其中以下内容的出现受条件性支持:
  • 字符 '
  • 字符 \
  • 字符序列 //
  • 字符序列 /*
q字符 - 源字符集 (C++23 前)翻译字符集 (C++23 起) 除了换行符和 " 以外的任何成员
记号序列 - 一个或多个预处理记号
字符串字面量 - 一个字符串字面量
h记号序列 - 一个或多个除了 > 以外的预处理记号

说明

1) 在一系列由实现定义的地点中搜索由 h字符序列 唯一识别的头,并将该指令替换为这个头的全部内容。由实现定义如何制指定这些地点和识别头。
2) 将该指令替换为由 q字符序列 识别的源文件的全部内容。命名的源文件通过由实现定义的方式进行搜索。如果不支持这种搜索或者搜索失败,该指令按语法 (1) 重新处理,将原指令中包含的序列(包括大于号,如果存在)作为语法 (1) 中所需的序列。
3) 该指令中 include 后面的预处理记号会按在正常文本中进行处理(即每个目前定义为宏名的标识符都会被替换为它的预处理记号替换列表)如果在所有替换都完成后生成的指令不匹配前面两种语法,那么行为未定义。将在一对预处理记号 < 和 > 之间或一对 " 字符之前的预处理记号序列合并为单个头名预处理记号的方法由实现定义。
4) 搜索由 h字符序列q字符序列 识别的头或源文件,如同通过将该预处理记号序列作为语法 (3) 中的 记号序列 的方法进行搜索,但不会有后续的宏展开会执行。如果刚才的语法 (3) 指令不满足 #include 指令的语法要求,那么程序非良构。如果搜索成功,__has_include 表达式求值为 1,否则(搜索失败时)求值为 0。
5) 只有在不匹配 (4) 才会选择此形式,此时预处理记号会按在正常文本中进行处理。

如果由 头名(即 < h字符序列 >" q字符序列 ")标识的头指明了一个可以导入的头,那么由实现决定要包含这个头的 #include 预处理指令是否要被替换为具有以下形式的导入指令

import 头名 ; 换行

(C++20 起)

__has_include 可以在 #if #elif 中的表达式展开。它被 #ifdef #ifndef #elifdef #elifndef (C++23 起)defined 视为已定义的宏,但不能在其他任何地方使用。

注解

语法 (1) 的意图是搜索由实现所掌控的文件。典型实现仅搜索标准包含目录。这些标准包含目录中隐含地包含标准 C++ 库和标准 C 库。用户通常能通过编译器选项来控制标准包含目录。

语法 (2) 的意图是搜索不被实现所掌控的文件。典型实现首先于当前文件所在的目录搜索,然后退回使用语法 (1)。

包含一个文件时,它将经过翻译阶段 1-4 的处理,这可能递归地包含嵌套 #include 指令的展开,直到一个由实现定义的嵌套限制。为避免(可能传递性地)重复包含相同文件,和由文件包含自身造成的无限递归,通常使用头文件防护:整个头文件被包装在下列结构中

#ifndef FOO_H_INCLUDED /* 任何唯一地映射到文件的名称 */
#define FOO_H_INCLUDED
// 文件内容在此
#endif

许多编译器也会实现有类似效果的非标准 pragma #pragma once:在已经包含相同文件(文件的身份以操作系统指定的方式确定)的时候禁止处理该文件。

如果 h字符序列q字符序列 中包含了类似转义序列的序列,那么根据实现可能会导致错误,被处理为转义序列对应的字符,或者具有完全不同的含义。

结果是 1__has_include 只表明存在有指定名称的头或源文件。它并不意味着包含该头或源文件时不会导致错误,或它会包含任何有意义的内容。例如在同时支持 C++14 和 C++17 模式(并在其 C++14 模式作为一项遵从标准的扩展而提供 __has_include)的 C++ 实现上,__has_include(<optional>) 在 C++14 模式中可以是 1,但实际上 #include <optional> 可能导致错误。

示例

#if __has_include(<optional>)
#  include <optional>
#  define has_optional 1
   template<class T> using optional_t = std::optional<T>;
#elif __has_include(<experimental/optional>)
#  include <experimental/optional>
#  define has_optional -1
   template<class T> using optional_t = std::experimental::optional<T>;
#else
#  define has_optional 0
#  include <utility>
template<class V> class optional_t {
    V v_{}; bool has_{false};
  public:
    optional_t() = default;
    optional_t(V&& v) : v_(v), has_{true} {}
    V value_or(V&& alt) const& { return has_ ? v_ : alt; }
    /*...*/
};
#endif
 
#include <iostream>
 
int main()
{
    if (has_optional > 0)
        std::cout << "<optional> 存在\n";
    else if (has_optional < 0)
        std::cout << "<experimental/optional> 存在\n";
    else
        std::cout << "<optional> 不存在\n";
 
    optional_t<int> op;
    std::cout << "op = " << op.value_or(-1) << '\n';
    op = 42;
    std::cout << "op = " << op.value_or(-1) << '\n';
}

输出:

<optional> 存在
op = -1
op = 42

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 787 C++98 如果 h字符序列q字符序列 中包含了类似转义序列的序列,那么行为未定义 改为受条件性支持

参阅

C++ 标准库头文件列表