预处理命令

1、简介

编译阶段主要分为4个,分别是预处理、编译、汇编和链接。在预处理阶段处理预处理命令。

预处理命令以 # 号开头,只有空格和制表符可以出现在 # 号前面,也可以出现在#和命令之间。预处理命令一般独占一行,遇到本行的第一个换行符时结束。

预处理命令也可以跨行,可以在行尾放置一个 \ ,这样可以在下一行继续完成上一行的命令。预处理器会直接删除 \ 和换行符并且把两行命令连接在一行。 \ 必须是换行符之前的最后一个字符。因为预处理器会把注释替换成空格,所以如果在 \ 和换行符之间有注释,那么 \ 不会有换行作用。

2、插入头文件的内容

#include 命令告诉预处理器将指定头文件的内容插入到预处理命令的相应位置。有两种方式可以指定头文件,#include <文件名>#include "文件名"。使用尖括号指定的文件会在特定的目录下查找,例如在 Unix 系统中会在/usr/local/include 和 /usr/include 中查找,使用双引号指定的文件会在当前目录下查找,当前路径找不到也会去系统的include路径下查找。文件名可以包含路径,有路径后只会在该路径下查找。

3、定义和使用宏

可以使用#define来定义宏,该命令允许把名称指定成任何所需的文本,无论宏名称出现在源代码的何处,预处理器都会把它用定义时的文本替换掉(在字符串中宏名称不会被替换)

3.1 没有参数的宏

格式: #define 宏名称 替换文本

// 示例:
#define ARRAY_SIZE 100
double data[ARRAY_SIZE];

3.2 带参数的宏

格式:#define 宏名称( [形参列表] ) 替换文本

形参列表是用逗号隔开的多个标识符,它们都作为宏的形参。定义一个宏时,必须确保左括号( 和宏名称之间没有空白符,有空白时就会被识别为没有参数的宏。

当调用一个类函数宏时,预处理器会把调用时的实参取代替换文本中的形参,c99允许在调用时实参为空,这种情况下替换文本会删除形参,不是所有编译器都支持这种空实参的做法。

如果调用时的实参也包含宏,会先对它进行展开,然后才把该实参取代替换文本中的形参

#define DISTANCE(x, y)  ((x) >= (y)) ? (x) - (y) : (y) - (x)
d = DISTANCE(a, b + 0.5);
展开后如下所示
d = ((a) >= (b + 0.5) ? (a) - (b + 0.5) : (b + 0.5) - (a));
替换文本中所有出现的形参,应该使用括号将其包围。如果没有加括号表达式是 a - b + 0.5,于期望的运算不同。
3.2.1 可选参数

格式:#define 宏名称( [形参列表,] … ) 替换文本

省略号必须在参数列表的后面,以表示可选参数。预处理器会将所有可选参数连同分隔它们的逗号打包在一起作为一个参数,这个参数用标识符 __VA_ARGS__ 表示,该标识符只能用在替换文本中。

在 GCC 编译器中,这里## 的作用是如果可选参数为0时,去掉 frm 后面的逗号,否则编译报错,在MSVC编译器中就不需要##。

#define log(frm, ...) {\
    printf(frm, ##__VA_ARGS__);\
    printf("\n");\
}

也可以采用如下方式,在形参列表里自定义可变参数标识符。

#define log(frm, argss...) {\
    printf(frm, ##argss);\
    printf("\n");\
}

#define log(frm, args...) {\
    printf(frm, ##args);\
    printf("\n");\
}

3.3 # 号的作用

#的操作数必须是宏替换文本中的形参。当形参名称出现在替换文本中,并且具有前缀#字符时,预处理器会把于该形参对应的实参放到一对双引号中,形成一个字符串。实参中的所有字符本身维持不变,但一下几种情况例外:

  • 在实参各记号之间如果存在有空白符序列,都会被替换成一个空格符
  • 实参中每个双引号的前面都会添加一个反斜线
  • 实参中字符常量、字符串字面量中的每个反斜线前面,也会添加一个反斜线。但如果该反斜线本身使通用字符名的一部分,则不会再在其前面添加反斜线。通用字符名即为Unicode值,格式:\uXXXX 或 \UXXXXXXXX,XXXX 或 XXXXXXXX是采用16进制表示的Unicode编码值(万国码)
#define showArgs(...) puts(#__VA_ARGS__)
showArgs( one\n,          "2\n",     three);

//宏展开后如下
puts("one\n, \"2\\n\", three");
//终端输出如下
one
, "2\n", three

3.4 ## 号的作用

可以出现在宏的替换文本中。出现在##运算符前后的空白符连同##运算符本身一起被删除,把左右操作数结合在一起。如果结果文本中包含宏名称,则会继续进行替换。

#define TEXT_A "Hello, world"
#define msg(x) puts( TEXT_ ## x)
msg(A);
// 先把形参x替换成实参A,然后执行##运算符,结果如下
puts(TEXT_A);
// 继续替换,结果如下
puts("Hello, world");

没有实参的情况

msg();
//  展开后如下
puts( TEXT_ );

3.5 宏的作用域和重新定义

无法再次使用#define 命令重新定义一个已经被定义为宏的标识符,除非重新定义所使用的替换文本与已经被定义的替换文本完全相同,如果有形参,形参名称也必须一样。
如果想改变一个宏的内容,可以用如下命令取消宏的定义,再重新定义。该命令不需要指定参数列表。

#undef 宏名称

当某个宏首次遇到它的#undef命令时,作用域就会结束,没有该命令,作用域在宏所在的文件结束终止。

3.6 泛型宏

C11 标准新增了泛型选择,泛型选择以关键字 _Generic 开头,如下所示:

#define log10(x) _Generic((x),\
    long double : log10l,\
    float:        log10f,\
    default:      log10\
)(x)

如果实参为double或整数类型,宏最终结果为 log10(arg)

4、条件式编译

条件式编译命令指定预处理器依据特定的条件来判断保留或删除某段源代码。

条件式编译区域以 #if、#ifdef 或 #ifndef 等命令开头,以 #endif 命令结尾。编译区域类可以有多个 #elif 命令,但最多有一个 #else 命令。

以#if开头的条件式编译区域具有下面的格式:

#if 表达式1
    [组1]
[#elif 表达式2
    [组2]]
...
[#elif 表达式n
    [组n]]
[#elif
    [组n+1]]
#endif

组1、组2等代码段可以包含任何c/c++源代码,也可以包含嵌套的条件式编译命令。

常用示例:

// 可以保证同一个文件不会被包含多次,也可以保证内容完全相同的两个文件只包含一次
#ifndef INCFILE_H_
#define INCFILE_H_
 // 实际的头文件incfile.h的类容写在这里
#endif

4.1、defined 运算符

使用格式如下:
defined 标识符
defined (标识符)
如果标识符是宏名称,且该宏名称已被 #define 定义,未被 #undef 命令取消定义,则 defined 表达式会生成1,否则生成0

#if defined(__unix__) && defined(__GNUC__)
...
#endif

__unix__ 和 __GNUC__ 都是预定义的宏,用来识别目标系统和编译器

5、#pragma命令

格式:#pragma [tokens]

如果预处理器支持所指定的标记,就会执行这些标记所代表的动作,不支持就忽略该命令

例子:#pragma once
该命令可以保证同一个文件不会被包含多次,同一个文件指物理上的文件,不是指内容相同的两个文件

6、#line 命令 和 #error 命令

#line指令重置__LINE__和__FILE__宏报告的行号和文件名

#line 1000     // 把当前行号重置为1000
#line 10 "test.c" // 把行号重置为10,把文件名重置为test.c

无论是否有实际错误,#error命令都会让预处理器发出错误消息,语法如下:

#error [text]

如果上述命令存在可选项 text ,则 text就会被包含在预处理器的错误消息中。然后,编译器会终止处理源代码,并结束执行,仿佛遇到了严重错误。text中如果有其他宏,则不会被展开。
例子:

#ifndef 宏名称
    #error "错误提示"
#endif

7、_Pragma 运算符

格式:_Pragma (字符串)

_Pragma("use_bool \"true \"false")
变成了:
#pragma use_bool "true "false

8、预定义的宏

有很多预定义的宏,例子如下:

  • __DATE__ 它的替换文本是一个包含编译日期的字符串字面量,如期格式为"Mmm dd yyyy", 如:“Mar 19 2006”

  • __FILE__ 一个含有当前源代码文件名称的字符串字面量

  • __LINE__ 返回引用该宏地方的行号

  • __TIME__ 包含编译时间的字符串字面量,格式为"hh: mm: ss",如:“08:00:59”

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值