C预处理器

目录

1、明示常量#define

1.1 记号

1.2 重定义常量

2、在#define中使用参数

2.1 用宏参数创建字符串:#运算符

2.2 预处理器黏合剂:##运算符

2.3 变参宏:...和__VA_ARGS__

3、宏和函数的选择

4、文件包含:#include

5、其他指令

5.1 #undef指令

5.2 从C预处理器看已定义

5.3 条件编译

5.3.1 #ifdef、#else和#endif指令

5.3.2 #ifndef指令

5.3.3 #if和#elif指令

5.3.4 #line和#error

5.3.5 #pragma


1、明示常量#define

编译器把文本划分成预处理记号序列、空白序列和注释序列。(记号是由空格、制表符或换行符分隔的项。

每行#define都由3部分组成。第1部分是#define指令本身。第2部分是选定的缩写,也称为宏。有些宏被称为类对象宏,还有类函数宏。第3部分被称为替换列表或替换体。

宏的名称中不允许有空格,而且必须遵循C变量的命名规则:只能使用字符、数字和下划线,而且首字符不能是数字。     从宏变成最终替换文本的过程被称为宏展开。 宏可以表示任何字符串,甚至可以表示整个C表达式。

#define T 2
...
int x=t;
printf("%d",x);

这里的x被替换成了T中的值

#define F T*T
...
x=F;
printf("%d",x);

实际过程是x=T*T,即是2*2。预处理其不会进行实际的运算,这一过程在编译时进行。预处理器不做计算,不对表达式求值,它只进行替换。   宏定义还可以包含其他宏。

#define P "X is %d.\n"
...
printf(P,x);

实际变成了

printf("X is %d.\n",x);

如果替换体太长,我们在一行的结尾加一个反斜杠字符\ 使该行扩展至下一行。注意,第2行要与第1行左对齐。

一般而言,预处理器发现程序中的宏后,会用宏等价的替换文本进行替换。如果替换的字符串中还包含宏,则继续替换这些宏。唯一例外的是双引号中的宏。

printf("P");
//打印的是P,而不是X is %d.\n
//如果要打印该行,则这样写
printf("%s",P);

1.1 记号

可以把宏的替换体看作是记号型字符串,而不是字符型字符串。C预处理器记号是宏定义的替换体中单独的“词”。用空白把这些词分开。

#define FOUR 2*2
//有一个记号:2*2序列
#define SIX 2 * 3
//有3个记号:2、*、3

解释为字符型字符串,把空格视为替换体中的一部分;解释为记号型字符串,把空格视为替换体中个记号的分隔符。

顺带一提,C编译器处理记号的方式比预处理器复杂。由于编译器理解C语言的规则,所以不要求代码中用空格来分隔记号。例如,编译器可以把2*2直接视为3个记号,因为它可以识别2是常量, *是运算符。

1.2 重定义常量

假设先把 T 定义为20,稍后在该文件中又把它定义为25。这个过程称为重定义常量。不同的实现采用不同的重定义方案。除非新定义与旧定义相同,否则有些实现会将其视为错误,还有些会给出警告。ANSI标准采用第1种方案,只有新定义和旧定义完全相同才允许重定义。具有相同的定义意味着替换体中的记号必须相同,顺序也相同。

//定义相同
#define SIX 2 * 3
#define SIX 2 * 3
//这两条定义都有相同的记号,
//额外的空格不算替换体的一部分
#define SIX 2*3
//只有一个记号,因此与前两条定义不同

2、在#define中使用参数

在#define 中使用参数可以创建外形和作用与函数类似的类函数宏。

#define SQUARE(X) X*X
...
int z;
z=SQUARE(2);
//z=2*2

注意,预处理器不做计算,不对表达式求值,它只进行替换。这可能会造成一些错误。必要时,使用足够多的圆括号来确保运算和结合的正确顺序。

2.1 用宏参数创建字符串:#运算符

C允许在字符串中包含宏参数。在类函数宏的替换体中,#号作为一个预处理运算符,可以把记号转换成字符串。例如,如果x是一个宏形参,那么#x就是转换为字符串“x“的形参名。这个过程称为字符串化。

#include <stdio.h>
#define PSQ(x) printf("The square of " #x "is %d.\n",((x)*(x))

int main(void)
{
    int y=5;
    PSQ(y);
    PSQ(2+4);
    return 0;
}

调用第1个宏时,用“y”替换#x。调用第2个宏时,用“2+4”替换#x。

2.2 预处理器黏合剂:##运算符

##运算符  把两个记号组合成一个记号。

#define A(n)  x ## n

宏A(4)将展开为x4

#运算符组合字符串,##运算符把记号组合成为一个新的标识符。

2.3 变参宏:...和__VA_ARGS__

stdvar.h头文件提供了工具,让用户自定义带可变参数的函数。通过把宏参数列表中最后的参数写成省略号(3个点)来实现这一功能。预定义宏__VA_ARGS__可用在替换部分中,表明省略号代表什么。

#define PR(...) printf(__VA_ARGS__)
PR("Your");
PR("weight = %d\n",wt);

对于第1次调用,__VA_ARGS__展开为1个参数:"Your"

对于第2次调用,__VA_ARGS__展开为2个参数:"weight = %d\n"、wt。

展开后的代码是:

printf("Your");
printf("weight = %d\n",wt);

3、宏和函数的选择

宏和函数的选择实际上是时间和空间的权衡。宏生成内联代码,即在程序中生成语句。如果调用10次宏,即在程序中插入10行代码。如果调用函数10次,程序中只有一份函数语句的副本,所以节省了时间。然而另一方面,程序的控制必须跳转至函数内,随后再返回主调程序,显然比内联代码花费更多的时间。宏的一个优点是,不用担心变量类型,这是因为宏处理的是字符串,而不是实际的值。

4、文件包含:#include

当预处理器发现#include指令时,会查看后面的文件名并把文件的内容包含到当前文件夹中,即替换源文件中的#include指令。这相当于把被包含文件的全部内容输入到源文件#include指令所在的位置。

#include <stdio.h> //文件名再尖括号中
#include "mystuff.h" //文件名在双引号中

在UNIX系统中,尖括号告诉预处理器在标准系统目录中查找该文件。双引号告诉预处理器首先在当前目录中(或文件名指定的其他目录)查找该文件,如果未找到再查找标准系统目录。

5、其他指令

通过修改#define的值即可生成可移植代码。#undef指令取消之前的#define定义。#if、#ifdef、#ifndef、#else、#elif和#endif指令用于指定什么情况下编译那些代码。#line指令用于重置行和文件信息,#error指令用于给出错误消息,#pragma指令用于向编译器发出指令。

5.1 #undef指令

#define LIMIT 400
#undef LIMIT
//将移除上面#define定义。可以把LIMIT重新定义为一个新值。

5.2 从C预处理器看已定义

当预处理器在预处理指令中发现一个标识符时,它会把该标识符当作已定义的或未定义的。这里的已定义表示由预处理器定义。如果标识符是同一个文件中由前面的#define指令创建的宏名,而且没有用#undef指令关闭,那么该标识符是已定义的。如果标识符不是宏,假设是一个变量,那么该标识符对预处理器而言就是未定义的。

#define LIMIT 400 //是已定义的
int q;            //是未定义的
#undef LIMIT      //LIMI取消定义,是未定义的

#define宏的作用域从它在文件中的声明处开始,知道用#undef指令取消宏为止,或延伸至文件尾。

5.3 条件编译

5.3.1 #ifdef、#else和#endif指令
#ifdef A
    #include <string.h> 
//如果已经用#define定义了A,就执行上面的语句
#else
    #include <stdlib.h>
//如果没有用#define定义A,则执行上面的语句
#endif

预处理器不识别用于标记快的花括号({}),因此它用#endif来标记指令块。

5.3.2 #ifndef指令

#ifndef指令判断后面的标识符是否是未定义的,常用于定义之前未定义的常量。

//ar.h
#ifndef SIZE
    #define SIZE 100
#endif

#ifndef指令可以防止相同的宏被重复定义。在首次定义一个宏的头文件中用#ifndef指令激活定义,随后在其他文件中的定义都被忽略。

假设有上面的ar.h头文件,然后把下面一行代码放入一个头文件中:

#include "ar.h"
//SIZE被定义为100.但是把下面的代码放入该头文件:
#define SIZE 10
#include "ar.h"

SIZE则被设置为10。当执行到#include "ar.h"这行,处理ar.h中的代码时,由于SIZE是已定义的,所以就跳过了#define SIZE 100这行。

5.3.3 #if和#elif指令

#if指令很像C语言中的if。#if后面跟整型常量表达式,如果表达式为非零,则表达式为真。可以按照if else形式使用#elif。

#if SYS==1  //为真就执行下面一行代码
	#include "ibm.h"
#elif SYS==2
	#include "vax.h"
#else
	#inlcude "general.h"
#endif
5.3.4 #line和#error

#line指令重置_ _LINE_ _和_ _FILE_ _宏报告的行号和文件名。

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

#error指令让预处理器发出一条错误消息,该消息包含指令中的文本。

5.3.5 #pragma

#pragma把编译器指令放入源代码中,例如,在开发C99时,标准被称为C9X,可以使用下面的编译指示让编译器支持C9X:

#pragma c9x on

一般而言,编译器都有自己的编译指示集。编译指示可能用于控制分配给自动变量的内存量,或者设置错误检查的严格程度,或者启用非标准语言特性等。

C99还提供_Pragma预处理器运算符,该运算符把字符串转换成普通的编译指示。_Pragma运算符完成“解字符串”的工作,即把字符串中的转义序列转换成它所代表的字符。

  • 43
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值