跳转至

预处理器

`module` 和 `import` 指令也是预处理指令。(C++20 起)

预处理器有一下几类 - 有条件编译源文件的某些部分(由 #if、#ifdef、#ifndef、#else、#elif、#elifdef、#elifndef (C++23 起) 和 #endif 指令控制)。 - 替换 文本宏,同时可能对标识符进行拼接或加引号(由 #define 和 #undef 指令与 # 和 ## 运算符控制)。 - 包含 其他文件(由 #include 指令控制并以 __has_include 检查 (C++17 起))。 - 导致错误警告(C++23 起)(由指令 #error 或 #warning (C++23 起) 控制)。 - 由实现定义的行为(由 #pragma 指令和 _Pragma 运算符 (C++11 起) 控制)。 - 对预处理器可用的文件名和行信息(由 #line 指令控制)

1 条件包含

- `#if、#elif、#else`是一组。
- `#ifdef、#ifndef、#elifdef、#elifndef、#endif`是一组。
  • 语法如下:
语法 注释
#if 表达式 #if 0 这个条件是假
#elif 表达式
#else
#ifdef 标识符 等价于 #if defined
#ifndef 标识符 等价于 #if !defined
#elifdef 标识符 (C++23 起) 等价于#elif defined
#elifndef 标识符 (C++23 起) 等价于 #elif !defined
#endif
  • 示例:
#define ABCD 2
#include <iostream>

int main()
{

#ifdef ABCD
    std::cout << "1: yes\n";
#else
    std::cout << "1: no\n";
#endif

#ifndef ABCD
    std::cout << "2: no1\n";
#elif ABCD == 2
    std::cout << "2: yes\n";
#else
    std::cout << "2: no2\n";
#endif

#if !defined(DCBA) && (ABCD < 2*4-3)
    std::cout << "3: yes\n";
#endif


// 注意若编译器不支持 C++23 的 #elifdef/#elifndef 指令则会选择“不期待”块(见后述)。
#ifdef CPU
    std::cout << "4: no1\n";
#elifdef GPU
    std::cout << "4: no2\n";
#elifndef RAM
    std::cout << "4: yes\n"; // 期待的块
#else
    std::cout << "4: no!\n"; // 由于跳过未知的指令不期待地选择此块
                             // 并直接从 "#ifdef CPU" “跳”到此 "#else" 块
#endif

// 为修复此问题,我们可以条件性地仅若支持 C++23 指令 #elifdef/#elifndef
// 才定义 ELIFDEF_SUPPORTED 宏。
#if 0
#elifndef UNDEFINED_MACRO
#define ELIFDEF_SUPPORTED
#else
#endif

#ifdef ELIFDEF_SUPPORTED
    #ifdef CPU
        std::cout << "4: no1\n";
    #elifdef GPU
        std::cout << "4: no2\n";
    #elifndef RAM
        std::cout << "4: yes\n"; // 期待的块
    #else
        std::cout << "4: no3\n";
    #endif
#else // 不支持 #elifdef 时使用累赘的旧 `#elif defined`
    #ifdef CPU
        std::cout << "4: no1\n";
    #elif defined GPU
        std::cout << "4: no2\n";
    #elif !defined RAM
        std::cout << "4: yes\n"; // 期待的块
    #else
        std::cout << "4: no3\n";
    #endif
#endif
}

2 文本替换宏

  • 语法: | 语法 | 注释 | | ------------------------------------ | ---------- | | #define 标识符 替换列表(可选) | | | #define 标识符(形参) 替换列表(可选) | | | #define 标识符(形参, ... )替换列表(可选) | (C++11 起) | | #define 标识符( ... )替换列表(可选) | (C++11 起) | | #undef 标识符* | |

3 源文件包含

`#include`只是把其它文件*完整的拷贝到*当前文件中。(在#include行的位置替换)
  • 功能:将其他源文件包含到当前源文件中紧随指令之后的一行。
  • 语法: | 语法 | 注释 | | ------------------------------------------------------------ | ---------- | | #include < h字符序列 > 换行 | | | #include " q字符序列 " 换行 | | | #include 记号序列 换行 | | | __has_include ( " q字符序列 " )__has_include ( < h字符序列 > ) | (C++17 起)  检查一个头或源文件是否可以被包含。| | __has_include ( 字符串字面量 )__has_include ( < h记号序列 > ) | (C++17 起) |

  • 示例

#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