跳到主要内容

源文件包含

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

语法

1#include
<h-char-sequence>new-line
2#include
"q-char-sequence"new-line
3#include
pp-tokensnew-line
4__has_include
__has_include
(h-char-sequence)
"q-char-sequence"
 (自 C++17 起)
5__has_include
__has_include
(string literal)
<h-pp-tokens>
 (自 C++17 起)
  1. 搜索一个由 h-char-sequence 唯一标识的头文件,并用该头文件的全部内容替换该指令。
  2. 搜索一个由 q-char-sequence 标识的源文件,并用该源文件的全部内容替换该指令。它可能会回退到 (1) 并将 q-char-sequence 视为头文件标识符。
  3. 如果 (1)(2) 均不匹配,则 pp-tokens 将进行宏替换。宏替换后的指令将再次尝试与 (1)(2) 匹配。
  4. 检查头文件或源文件是否可包含。
  5. 如果 (4) 不匹配,则 h-pp-tokens 将进行宏替换。宏替换后的指令将再次尝试与 (4) 匹配。
pubnew-line换行符
pubh-char-sequence一个或多个 h-chars 的序列,其中出现以下任何一种字符序列时,其语义由实现定义(条件支持):
  • 字符 '
  • 字符 "
  • 字符 \
  • 字符序列//
  • 字符序列/*
pubh-char除了换行符和 > 之外的,源字符集 (直到 C++23)翻译字符集 (自 C++23 起)的任何成员
pubq-char-sequence一个或多个 q-chars 的序列,其中出现以下任何一种字符序列时,其语义由实现定义(条件支持):
  • 字符 '
  • 字符 \
  • 字符序列//
  • 字符序列/*
pubq-char除了换行符和 " 之外的,源字符集 (直到 C++23)翻译字符集 (自 C++23 起)的任何成员
pubpp-tokens一个或多个 预处理标记的序列
pubstring-literal一个 字符串字面量
pubh-pp-tokens一个或多个预处理标记的序列,但不包括 >

解释

  1. 在实现定义的目录中搜索由 h-char-sequence 唯一标识的头文件,并用该头文件的全部内容替换该指令。搜索目录的指定方式以及头文件的标识方式是实现定义的。
  2. 用由 q-char-sequence 标识的源文件的全部内容替换该指令。命名源文件以实现定义的方式进行搜索。如果不支持此搜索,或搜索失败,则该指令将重新处理,就像它从原始指令中读取语法 (1) 一样,并包含相同的序列(包括 > 字符,如果有的话)。
  3. 指令中 include 后面的预处理标记将像在普通文本中一样进行处理(即,当前被定义为宏名称的每个标识符都将被其替换列表的预处理标记替换)。如果所有替换后的指令不匹配前面两种形式之一,则行为是未定义的。将尖括号 < > 或引号 " " 之间的预处理标记序列组合成单个头文件名预处理标记的方法是实现定义的。
  4. 作为 (3) 中的 pp-tokens,搜索由 h-char-sequenceq-char-sequence 标识的头文件或源文件,但不再进行宏扩展。如果这样的指令不满足 #include 指令的语法要求,则程序格式不正确。__has_include 表达式如果成功搜索到源文件,则求值为 1,如果搜索失败,则求值为 0
  5. 此形式仅在 (4) 不匹配时考虑,在这种情况下,预处理标记将像在普通文本中一样进行处理。

如果由 header-name(即 < h-char-sequence >" q-char-sequence ")标识的头文件表示一个可导入的头文件,则 #include 预处理指令是否被替换为 import header-name ; new-line 形式的 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 /* any name uniquely mapped to file name */
#define FOO_H_INCLUDED
// contents of the file are here
#endif

许多编译器也实现了非标准的 pragma#pragma once具有类似的效果:如果同一个文件(文件身份由操作系统特定方式确定)已被包含,它会禁用对该文件的处理。

q-char-sequenceh-char-sequence 中,类似于转义序列的字符序列可能会导致错误、被解释为转义序列对应的字符,或者根据实现的不同具有完全不同的含义。

__has_include 的结果为 1 仅表示存在具有指定名称的头文件或源文件。它并不意味着包含该头文件或源文件时不会导致错误或包含有用的内容。例如,在支持 C++14 和 C++17 模式(并在其 C++14 模式下作为符合标准的扩展提供 __has_include)的 C++ 实现中,在 C++14 模式下 __has_include(<optional>) 可能为 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> is present\n";
else if (has_optional < 0)
std::cout << "<experimental/optional> is present\n";
else
std::cout << "<optional> is not present\n";

optional_t<int> op;
std::cout << "op = " << op.value_or(-1) << '\n';
op = 42;
std::cout << "op = " << op.value_or(-1) << '\n';
}
结果
<optional> is present
op = -1
op = 42

缺陷报告

以下改变行为的缺陷报告已追溯应用于先前发布的 C++ 标准。

DR应用于发布时的行为正确行为
CWG 787C++98如果转义序列是
q-char-sequenceh-char-sequence 中类似
它是条件支持的

源文件包含

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

语法

1#include
<h-char-sequence>new-line
2#include
"q-char-sequence"new-line
3#include
pp-tokensnew-line
4__has_include
__has_include
(h-char-sequence)
"q-char-sequence"
 (自 C++17 起)
5__has_include
__has_include
(string literal)
<h-pp-tokens>
 (自 C++17 起)
  1. 搜索一个由 h-char-sequence 唯一标识的头文件,并用该头文件的全部内容替换该指令。
  2. 搜索一个由 q-char-sequence 标识的源文件,并用该源文件的全部内容替换该指令。它可能会回退到 (1) 并将 q-char-sequence 视为头文件标识符。
  3. 如果 (1)(2) 均不匹配,则 pp-tokens 将进行宏替换。宏替换后的指令将再次尝试与 (1)(2) 匹配。
  4. 检查头文件或源文件是否可包含。
  5. 如果 (4) 不匹配,则 h-pp-tokens 将进行宏替换。宏替换后的指令将再次尝试与 (4) 匹配。
pubnew-line换行符
pubh-char-sequence一个或多个 h-chars 的序列,其中出现以下任何一种字符序列时,其语义由实现定义(条件支持):
  • 字符 '
  • 字符 "
  • 字符 \
  • 字符序列//
  • 字符序列/*
pubh-char除了换行符和 > 之外的,源字符集 (直到 C++23)翻译字符集 (自 C++23 起)的任何成员
pubq-char-sequence一个或多个 q-chars 的序列,其中出现以下任何一种字符序列时,其语义由实现定义(条件支持):
  • 字符 '
  • 字符 \
  • 字符序列//
  • 字符序列/*
pubq-char除了换行符和 " 之外的,源字符集 (直到 C++23)翻译字符集 (自 C++23 起)的任何成员
pubpp-tokens一个或多个 预处理标记的序列
pubstring-literal一个 字符串字面量
pubh-pp-tokens一个或多个预处理标记的序列,但不包括 >

解释

  1. 在实现定义的目录中搜索由 h-char-sequence 唯一标识的头文件,并用该头文件的全部内容替换该指令。搜索目录的指定方式以及头文件的标识方式是实现定义的。
  2. 用由 q-char-sequence 标识的源文件的全部内容替换该指令。命名源文件以实现定义的方式进行搜索。如果不支持此搜索,或搜索失败,则该指令将重新处理,就像它从原始指令中读取语法 (1) 一样,并包含相同的序列(包括 > 字符,如果有的话)。
  3. 指令中 include 后面的预处理标记将像在普通文本中一样进行处理(即,当前被定义为宏名称的每个标识符都将被其替换列表的预处理标记替换)。如果所有替换后的指令不匹配前面两种形式之一,则行为是未定义的。将尖括号 < > 或引号 " " 之间的预处理标记序列组合成单个头文件名预处理标记的方法是实现定义的。
  4. 作为 (3) 中的 pp-tokens,搜索由 h-char-sequenceq-char-sequence 标识的头文件或源文件,但不再进行宏扩展。如果这样的指令不满足 #include 指令的语法要求,则程序格式不正确。__has_include 表达式如果成功搜索到源文件,则求值为 1,如果搜索失败,则求值为 0
  5. 此形式仅在 (4) 不匹配时考虑,在这种情况下,预处理标记将像在普通文本中一样进行处理。

如果由 header-name(即 < h-char-sequence >" q-char-sequence ")标识的头文件表示一个可导入的头文件,则 #include 预处理指令是否被替换为 import header-name ; new-line 形式的 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 /* any name uniquely mapped to file name */
#define FOO_H_INCLUDED
// contents of the file are here
#endif

许多编译器也实现了非标准的 pragma#pragma once具有类似的效果:如果同一个文件(文件身份由操作系统特定方式确定)已被包含,它会禁用对该文件的处理。

q-char-sequenceh-char-sequence 中,类似于转义序列的字符序列可能会导致错误、被解释为转义序列对应的字符,或者根据实现的不同具有完全不同的含义。

__has_include 的结果为 1 仅表示存在具有指定名称的头文件或源文件。它并不意味着包含该头文件或源文件时不会导致错误或包含有用的内容。例如,在支持 C++14 和 C++17 模式(并在其 C++14 模式下作为符合标准的扩展提供 __has_include)的 C++ 实现中,在 C++14 模式下 __has_include(<optional>) 可能为 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> is present\n";
else if (has_optional < 0)
std::cout << "<experimental/optional> is present\n";
else
std::cout << "<optional> is not present\n";

optional_t<int> op;
std::cout << "op = " << op.value_or(-1) << '\n';
op = 42;
std::cout << "op = " << op.value_or(-1) << '\n';
}
结果
<optional> is present
op = -1
op = 42

缺陷报告

以下改变行为的缺陷报告已追溯应用于先前发布的 C++ 标准。

DR应用于发布时的行为正确行为
CWG 787C++98如果转义序列是
q-char-sequenceh-char-sequence 中类似
它是条件支持的