C语言之预处理器的使用

文章目录

    • 预定义符号
    • #define
    • #define 替换
    • 宏与函数
    • 带副作用的宏参数
    • 命名约定
    • #undef
    • 命令行定义
    • 条件编译
    • 是否被定义
    • 文件包含

预定义符号

   由预处理器定义的符号如下:

符号 样例值 含义
__FILE__ “name.c” 进行编译的源文件名
__ LINE__ 25 文件当前的行号
__DATE__ “Jan 31 1997” 文件被编译的日期
__TIME__ “18:04:30” 文件被编译的时间
__STDC__ 1 如果编译器遵循ANSI C ,其值就为1,否则未定义

#define

   你在这之前可能已经见过#define的一些简单用法,就是为数值命名一个符号。
下面,为你介绍它的更多用途。

#define name  stuff

   有了这个指令后,每当有符号name出现在这条指令后面,预处理器就会把它替换为stuff。

    K&R C
早期的C 编译器要求#出现在每行的起始位置,不过它的后面可以跟一些空白。在ANSI C 中,这条限制被取消了。

   替换文本并不仅限于数值字面值常量。使用#define指令,你可以把任何文本替换到程序中。
如:

#define reg    register
#define do_forever   for(;;)
#define CASE     break;case

   第一个只是为register创建一个简单的别名。第二个用更具有描述性的符号来代替一种用于实现无限循环的for语句。最后一个,用于switch语句中。

   如果定义中的stuff非常长,可以用\将它分为几行。
如下:

#define DEBUG_PRINT    printf("File %s line %d:"\
							"x = %d,y = %d,z = %d,"\
							__FILE__,__LINE__,\
							x,y,z)

我们可以这程序中这样使用它:

 x *= 2;
 y += x;
 z = x*y;
 DEBUG_PRINT;

注意:
DEBUG_PRINT后面加了一个分号,所以你不应该在宏定义的尾部加上分号。
否则会产生两条语句。在if else 的情况下容易出错(没有{ })

   #define 机制包括一个规定,允许把参数替换带文本中,这种实现通常称为宏(macro)定义宏(defined macro)。下面是其声明方式:

#define name(parameter-list)   stuff

   其中,parameter_list (参数列表)是一个由逗号分隔的符号列表,它们可能出现在stuff中。参数列表的左括号必须与name紧邻(不允许出现空格)。
   当宏被调用时,名字后面是一个由逗号分隔的值的列表,每个值都与宏定义中的一个参数相对应,整个列表用一对括号包围。当参数出现在程序中时,与每个参数对应的实际值都将被替换到stuff中。
   C语言之预处理器的使用_第1张图片

#define SQUARE(x)  x*x

   这样,你在程序中使用SQUARE(5),它将被替换为5*5。

注意:
当调用SQUARE(a+1)时,将出现问题,它被替换为 a+1*a+1  结果为2*a+1;
正确的做法是加上两个括号
#define SQUARE(x)   (x)*(x) 

但是,还有问题。如下:
#define DOUBLE(x)  (x)+(x)
a = 5;
printf("%d\n",10*DOUBLE(a));

10*DOUBLE(a) --> 10*(5) + (5)   由于*具有更高的优先级,我们仍然得不到我们所认为的结果。
我们应该在加上一对括号
((x)+(x))  

通过这些栗子,我们可以看出预处理器做的事情十分的简单粗暴。

#define 替换

   当程序中扩展#define 定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含了任何由#define 定义的符号,如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值所替代。
  3. 最后,再次扫描文本,看看是否包含任何由#define 定义的符号。如果是,就重复上面的处理过程。

宏参数和#define定义可以包含其他#define定义的符号。但是,宏不可以出现递归。
当预处理器搜索#define定义的符号时,字符串常量的内容不进行检查。你如果想把宏参数插入到字符串常量中,可以使用两种技巧。首先,领近字符串自动连接的特性使我们很容易把一个字符串分成几段,每段实际上都是一个宏参数。

#define PRINT(FORMAT,VALUE) 
		printf("The value is " FORMAT "\n",VALUE)
	...
PRINT("%d",x+3);

   这种技巧只有当字符串常量作为宏参数给出时才能使用。

   第二个技巧使用预处理器将一个宏参数转换为一个字符串。#argument 这种结构被预处理器翻译为"argument"。这种翻译可以让你这些编写代码:

#define ADD_TO_SUM(sum_number,value)  \
		sum ## sum_number  += value
...
ADD_TO_SUM(5,25);
这条语句将25加到变量sum5。这种连接必须产生一个合法的标识符。

宏与函数

   宏非常频繁地用于执行简单的计算,比如比较两个表达式中较大的一个。

#define MAX(a,b)  ((a)>(b) ? (a):(b))

为什么不用函数呢?

  1. 首先,用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大。
  2. 函数参数必须声明为一种特定的类型,而上面这个宏可以用于整型、长整型、单浮点型、双浮点型以及其他任何可以用 > 操作符比较大小的类型。
#define MALLOC(n,tupe)  \
			( (type *)malloc( (n)*sizeof(type) ) )
函数无法使用类型作为参数,但是宏可以。
pi = MALLOC(25,int);

带副作用的宏参数

   当宏参数在宏定义中出现的次数超过一定次数时,如果这个参数具有副作用,那么当你使用这个宏时就可能出现危险。
   比如我们 用 x+1 和 x++;
后者便会产生符作用。

#define MAX(a,b)  (  (a)>(b) ? (a):(b) )
...
x = 5;
y = 8;
z = MAX(x++,y++);
printf("x=%d,y=%d,z=%d\n",x,y,z);

这个程序打印的结果为 x=6,y=10,z=9。

z = ( (x++)>(y++)  ?  (x++):(y++) );

为什么那个加大的值增值了两次?注意后面的 (x++)。
类似于这些的有副作用需要小心使用。

命名约定

为了和函数区分开:命名约定十分重要。
一种常见的约定便是使用全部大写。

#undef

下面这条预处理指令用于移除一个宏定义。

#undef   name

   如果一个现存的名字需要被重新定义,那么它的旧定义首先必须用#undef移除。

命令行定义

   许多C编译器提供了允许你在命令行中定义符号用于启动编译过程。
   在UNIX编译器中,-D选项可以完成这些任务。

-Dname           //name 值默认为1
-Dname=stuff  //name = stuff

cc -DARRAY_SIZE=100 prog.c

条件编译

条件编译(conditional compilation):
选择某条语句或者某组语句进行翻译或者被忽略。

#if constant-expression
   		statements
#endif

其中constant-expression(常量表达式)由预处理器进行求值。如果值为非零值,那么statements部分就被正常编译,否则就忽略它们。

#if DEBUG
	printf(" x=%d , y=%d\n",x,y);
#endif

不管我们是想编译(1)还是忽略它(0),使用

#define DEBUG  1

我们可以将这个与命令行定义组合起来使用,通过-DDEBUG=0 , -DDEBUG=0 来选择(在IDE中就没有必要了)

#if指令还有可选的#elif 和 #else

#if cosntant-expression
	statements
#elif constant-expression
	other statements...
#else 
	other statements
#endif

是否被定义

   测试一个符号是否已被定义是非常有必要的。
我们可以通过以下这些方式进行:

#if  defined(symbol)
#ifdef  symbol

#if !defined(symbol)
#ifndef symbol

   每对定义的两条语句都是等价的,但#if形式功能更加强。因为常量表达式可能包含额外的条件,如:

#if X>0 || defined( ABC ) && defined( BCD )

嵌套指令

#if		defined(OS_UNIX)
		#ifdef OPTION1
				unix_version_of_option1();
		#endif	/*OS_UNIX*/
		#ifdef OPTION2
				unix_version_of_option2();
		#endif
#elif defined(OS_MSDOS)
		#ifdef OPTION2
			msdos_version_of_option2();
		#endif  /*OS_MSDOS*/
#endif				

文件包含

文件包含分为:函数库文件包含和本地文件包含(实际区别很小)

#include 
#include "game.h"
<>比如UNIX ,/user/include(标准位置)目录查找函数库头文件。
""的一种策略就是先在源文件的当前目录查找,然后像查找库文件一样在标准位置查找

为了避免包含同一个头文件多次,我们使用

#ifndef  _HEADERNAME_H
#define _HEADERNAME_H 1
/*
..
..
..
*/
#endif

你可能感兴趣的:(c语言)