C++预处理指令

指令

预处理指令控制预处理器的行为。每个指令占据一行并拥有下列格式:

  • # 字符
  • 预处理指令(define、undef、include、if、ifdef、ifndef、else、elif、endif、line、error、pragma 之一)
  • 实参(取决于指令)
  • 换行符

条件包含:

  • 预处理器支持有条件地编译源文件的某些部分。这一行为由 #if#else#elif#ifdef#ifndef #endif 指令所控制。

条件的求值:

  • #if, #elif

检查标识符是否被定义为宏名

“#ifdef 标识符”与“#if defined 标识符”实质上等价。

“#ifndef 标识符”与“#if !defined 标识符”实质上等价。

#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

文本替换宏:

语法

对于版本 (3,4),替换列表 可以含有记号序列“__VA_OPT__ ( 内容 )”,若 __VA_ARGS__ 非空,则它会被 内容 替换,否则不展开成任何内容。

#define 标识符 替换列表(可选)

(1)

#define 标识符( 形参 ) 替换列表(可选)

(2)

#define 标识符( 形参, ... ) 替换列表(可选)

(3)

#define 标识符( ... ) 替换列表(可选)

(4)

#undef 标识符

(5)

#  ## 运算符

仿函数宏中,替换列表 中放在标识符前的 # 运算符,使标识符运行形参替换,并将其结果以引号包围,实际上创建一个字符串字面量。另外,预处理器为内嵌的字符串字面量(若它存在)外围的引号添加反斜杠以进行转义,并按需要双写字符串中的反斜杠。

#undef 指令

#undef 指令取消定义 标识符,即取消 #define 指令所作的 标识符 定义。若标识符未关联到宏,则忽略该指令

__cplusplus

 

 

代表所用的 C++ 标准版本,展开成值 199711L(C++11 前)  201103L(C++11)  201402L(C++14)  201703L(C++17)  202002L(C++20)

(宏常量)

__STDC_HOSTED__

若实现有宿主(运行在 OS 下)则展开成整数常量 1,若实现自立(不随 OS 运行)则展开成 ​0​

_FILE__

展开成当前文件名,作为字符串字面量,可用 #line 指令更改

__LINE__

展开成源文件行号,整数常量,可用 #line 指令更改

__DATE__

展开成翻译日期,形式为 "Mmm dd yyyy" 的字符串。若月中日期数小于 10 则 "dd" 的首字符为空格。月份名如同以 std::asctime() 生成

__TIME__

展开成翻译时间,形式为 "hh:mm:ss" 的字符串字面量

__STDCPP_DEFAULT_NEW_ALIGNMENT__

展开成 std::size_t 字面量,其值为对不具对齐的 operator new 的调用所保证的对齐

__STDC__

若存在则为实现定义值,典型地用于指示 C 遵从性

__STDC_VERSION__

若存在则为实现定义值

__STDC_ISO_10646__

若 wchar_t 使用 Unicode ,则展开成 yyyymmL 形式的整数常量,日期指示所支持的 Unicode 的最近版本

__STDC_MB_MIGHT_NEQ_WC__

若对于基本字符集成员 'x' == L'x' 可能为假,则展开成 1,如在基于 EBCDIC 并且为 wchar_t 使用 Unicode 的系统上。

__STDCPP_STRICT_POINTER_SAFETY__

若实现支持严格 std::pointer_safety 则展开成 1

__STDCPP_THREADS__

若程序能拥有多于一个执行线程则展开成 1

__func__

注意:在每个函数体的作用域内部,都有一个名为 __func__ 的特殊的函数局域预定义变量,定义为一个持有具有实现定义格式的函数名的静态字符数组。它不是预处理器宏,但它与 __FILE__ 和 __LINE__ 一起使用

#define F(...) f(0 __VA_OPT__(,) __VA_ARGS__)
#define G(X, ...) f(0, X __VA_OPT__(,) __VA_ARGS__)
#define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ })
F(a, b, c) // 替换为 f(0, a, b, c)
F()        // 替换为 f(0)
G(a, b, c) // 替换为 f(0, a, b, c)
G(a, )     // 替换为 f(0, a)
G(a)       // 替换为 f(0, a)
SDEF(foo);       // 替换为 S foo;
SDEF(bar, 1, 2); // 替换为 S bar = { 1, 2 };

# 出现于 __VA_ARGS__ 之前时,展开后的 __VA_ARGS__ 整体被包在引号中:

#define showlist(...) puts(#__VA_ARGS__)
showlist();            // 展开成 puts("")
showlist(1, "x", int); // 展开成 puts("1, \"x\", int"

// 制造函数工厂并使用它
#define FUNCTION(name, a) int fun_##name() { return a;}
 
FUNCTION(abcd, 12)
FUNCTION(fff, 2)
FUNCTION(qqq, 23)
 
#undef FUNCTION
#define FUNCTION 34
#define OUTPUT(a) std::cout << "output: " #a << '\n'
 
int main()
{
    std::cout << "abcd: " << fun_abcd() << '\n';
    std::cout << "fff: " << fun_fff() << '\n';
    std::cout << "qqq: " << fun_qqq() << '\n';
    std::cout << FUNCTION << '\n';
    OUTPUT(million);               // 注意缺少引号
}

源文件包含:

语法

#include <文件名>

(1)

#include "文件名"

(2)

__has_include ( " 文件名 " )

__has_include ( < 文件名 > )

(3)

(C++17 起)

容许任何预处理记号(宏常量或表达式)作为给 #include 和 __has_include (C++17 起)的实参,只要它们展开成 < > 或 " " 所环绕的字符序列即可。

解释

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

2) 以由实现定义的方式搜索文件。此语法的意图是搜索不被实现所掌控的文件。典型实现首先于当前文件所在的目录搜索,然后仅当找不到文件时,才在 (1) 中的标准包含目录搜索。

3) 预处理器常量表达式,若找到文件名则求值为 1,而若找不到则求值为 ​0​。若其实参不是给 #include 指令的有效实参,则程序非良构。

#pragma once若已包含了同一文件(这里以特定于 OS 的方式确定文件的身份),则禁止处理该文件。

#if __has_include(<optional>)
#  include <optional>
#  define have_optional 1
namespace guard { using std::optional; }
#elif __has_include(<experimental/optional>)
#  include <experimental/optional>
#  define have_optional 1
#  define experimental_optional 1
namespace guard { using std::experimental::optional; }
#else
#  define have_optional 0
#endif
int main()
{
    if (have_optional)
        std::cout << "<optional> is present.\n";
    int x = 42;
#if have_optional == 1
    guard::optional<int> i = x;
#else
    int* i = &x;
#endif
    std::cout << "i = " << *i << '\n';
}

错误指令

语法

#error 错误消息

解释

遇到 #error 指令后,实现显示诊断消息 错误消息,并使程序非良构(编译停止)。

错误消息可由多个词组成,不必在引号中。

实现定义的行为控制

实现定义的行为受 #pragma 指令控制。

语法

#pragma 语用形参

(1)

_Pragma ( 字符串字面量 )

(2)

(C++11 起)

1) 具有由实现定义的行为方式

2)  字符串字面量 移除 L 前缀(若存在),外层引号,及前导/尾随空白符,将每个 \" 替换为 "、将每个 \\ 替换为 \,然后将结果记号化(如翻译阶段 3 中一样),再如同将结果作为 (1) 中的输入来予以使用。

 

#pragma once

#pragma once 是受到绝大多数现代编译器支持的非标准语用。当某个头文件中包含它时,指示编译器只对其分析一次,即使它在同一源文件中(直接或间接)被包含了多次也是如此。

 

#pragma pack

此 pragma 族控制后继定义的结构体、联合体、类的最大对齐。

#pragma pack(实参)

(1)

#pragma pack()

(2)

#pragma pack(push)

(3)

#pragma pack(push, 实参)

(4)

#pragma pack(pop)

(5)

其中 arg 实参是小的 2 的幂,指定以字节计的新对齐。

1) 设置当前对齐为值 实参 

2) 设置当前对齐为默认值(由命令行选项指定)。

3) 推入当前对齐的值到内部栈。

4) 推入当前对齐的值到内部栈然后设置当前对齐为值 实参 

5) 从内部栈弹出顶条目然后设置(恢复)当前对齐为该值。

#pragma pack 可以指定结构的对齐,然而它不能使结构过对齐。

文件名和行信息

更改预处理器中的当前行号和文件名。

语法

#line 行号

(1)

#line 行号 "文件名"

(2)

解释

1) 将当前预处理器行号更改为 行号。此点之后,宏 __LINE__ 的展开将产生 行号 加上自此遇到的实际代码行数。

2) 还将当前预处理器文件名更改为 文件名。此点之后,宏 __FILE__ 的展开将生成 文件名

#include <cassert>
#define FNAME "test.cc"
int main()
{
#line 777 FNAME
        assert(2+2 == 5);
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值