浅谈C语言预处理指令

第一次写这种博客的文章,结合《C primer Plus》以及自己的一些理解编写,如有写的不对的地方还望指出。

在C语言中,预处理指令大致包含#include、#define、#if、#ifdef、#ifndef、#else、#elif、#endif、#undef、#line、#error、#pragma等,下面就来细细 地了解一下各自的用法和语法明细。
1. #define指令主要用于定义明示常量(也称为符号常量),如我们写一个:

#define TWO 2

其中TWO就是一个宏,2是替换体,也叫做替换列表,我们通常会将宏写成大写,下面再介绍 类函数宏的时候也是如此,大写的目的除了表明这是一个宏定义,在检查程序时方便外,还提醒我们在使用的时候要注意这是一个宏,要小心使用,这一点在类函数宏中尤其明显,定义有误就和预期不太一样,当然这都在后面会有介绍。编译器只因在程序中多看了宏一眼,就会用替换体代替宏,值得注意的是在旧版的C要求指令一行从#开始,#和其他指令之间不能有空格,但这一要求在ANSI以及之后的标准都放开了,也就是说你可以在#加个空格或制表符,具体操作会在#if系列说到。
从宏到替换体的过程叫宏展开。在使用标准C注释时,注释部分会被代替为一个空格,这是编译器的使命,如:

#define/*这是一个注释*/TWO 2 ==> #define TWO 2 

其实我们可以把宏定义的替换体看做是记号字符串,C预处理器视宏定义的替换体成单独的“词”,用空白把这些词分开,如:

#define SIX 2 * 3
#define SIX 2*3

这是两个不同的定义,第一个表达式中的替换题包括2、*、3三个记号,而第二个表达式中包含了2*3这一个记号,虽然记号不同,但并不能就这么改变SIX的宏定义,如果我们还是想改变SIX的宏定义,就需要用到下一个预处理指令:#undef。我们不仅可以用#define来定义一个明示常量,还可以用来定义一个“函数“,我们来看一下下面的一个例子:

#define MEAN(X, Y) (((X) + (Y)) / 2)

我们定义了这么一个类函数宏之后就可以像一个函数一样调用了,比如我传进一个MEAN(2, 2),那么就会得到一个结果:2。注意在此处我是将每一个参数都加了一个括号的,那么不加括号的后果是什么呢?我们来看一下。我们写一个如下所示的类函数宏:

#define SQUARE(X) x*x

我们传进一个2+3,也就是说我们这么写:SQUARE(2+3),大家觉得结果会是什么呢?25还是11呢?答案是11,为什么呢?因为在运行时,编译器不会好心到帮你算好结果,什么样的表达式传进还什么样的表达式传出,还是原来的配方还是原来的味道,该表达式编译器会解释为:2+3*2+3,根据优先级,*是高于+的,所以会先计算2*3,就是2+6+3,结果就是11啦。所以在用的时候要注意到这一点。那么这么写和自己写个函数相比有什么优势呢?宏的一个优点是不用担心变量类型,熟悉C++的小伙伴们知道template模板,可以定义个类模板,函数模板,不用管变量类型,用的时候再告诉就行了。宏也一样,它在定义时是不需要写变量类型的,上面的定义一样,我在函数里传进一个float,int等都一样计算,若是要写一个函数的话,就需要更具不同的变量类型定义好几个函数,用宏写一行就可以了。另一个优点在于宏是内联的,在执行时是在代码中插入宏的定义的,而函数在被调用时是跳转的,比方说main里执行到哪个函数,就暂时先把main里放一边,跳到该子函数的定义处先执行,完了再回到main中执行。这样就花费了时间(跳转的过程时间较顺序执行的时间长一些),而且分配了内存给子函数,又耗时间又耗内存,谁有愿意干这种吃力不讨好的事呢?但注意的是只调用一次两次这两种方法所耗时间差的并不多,因为计算机执行的速度非常快,但当同一个函数要调用十几二十几次的时候,省时省内存的优势就很明显了。
2. #include是一个文件包含指令,该指令会把后面的文件内容包含到当前的文件中,其形式有两种:

#include <stdio.h>
#include "mystring.h"
#include "/usr/proj/myarray.h"      //查找/usr/proj目录

其区别在于:第一行的文件包含是告诉预处理器在标准系统目录中查找该文件,第二行告诉编译器现在当前目录中或者在文件名中指
定的其他目录中(具体代码实现见第三行)查找该文件,如果找不到就在标准系统目录下查找,头文件的重要性不言而喻,平时大家也要将
自己写的好的,常用的保存起来,方便复用,这里就不加赘述了。
3. #undef是一个用于取消已经#define过的指令,之前也提到过,要想重定义一个宏,要么等它的声明周期过了,要么先释放再定义,
当然,如果自己不知道该宏是否定义过也可以先#undef一下,免得出错
5. #if系列,这里我将#if、#ifdef、#ifndef、#else、#elif、#endif称为#if系列,其用法也很简单,类似于if判断

/*此处写为缩进格式,方便和if对应以及看着方便舒服,若编译器不支持还是都顶格写*/
#ifdef MAVIS
	#define NUM 2    //定义过MAVIS就执行该语句,否则执行下面的的语句
	#define ADD(X, Y) X+Y
#else
	#define NUM 3
	#define SUB(X, Y) X-Y 
#endif

与if相同的地方在于它的语意以及#else可以省略,不同地地方在于它不需要写花括号,并且#endif是不可少的。至于#ifndef其语意和#ifdef相反,ifndef是if not define的缩写,顾名思义,就是没定义啥就执行,否则就执行其他的,同样,在结尾处也需要加#endif。这两个常见的用法在于当我需要根据现实情况(如:在不同系统中需要定义不同的值或包含不同的文件)需要选择定义或包含头文件时,可以先#define一个宏,然后再写#ifdef或#ifndef,根据情况选择保留#define(相当于定义了)还是注释掉#define(相当于未定义)。最后的#if、#elif就类似于if、else if了,是条件选择,如:

#if SYS == 1
	#define NUM 1
#elif SYS == 2
	#define NUM 2
#elif SYS == 3
	#define NUM 3
#else 
	#define NUM 4
#endif

条件编译的好处在于我们使得代码具有了更好的可移植性,改变开头部分的几个关键字就可以 根据不同的系统设置不同的值和包含不同的文件
5. #line和#error:#line用于重置行号和文件名,#error用于发出一条错误消息,其具体用法如下:

/*#line用法*/
#line 1000      //将当前行号重置为1000
#line 100 "myprogram.c"   //将当前行号设置为100,文件名重置为myprogram.c

/*#error用法*/
#if __STDC_VERSION__ != 201112L   //__STDC_VERSION__是预处理宏,支持C99标准设置为199901L,支持C11标准设置为201112L
#error Not C11

#endif

6.#pragma是编译指示指令,例如在开发C99时,标准为C9X,可通过以下指令使得编译器支持C9X:

#pragma c9x on
  1. 其他。#运算符,如果x是一个宏形参,那么#x就是转换为字符串”x“的形参名,这个过程称为字符串化,具体看下面的例子:
#include <stdio.h>
#define NUM(x) printf(" "#x" is %d.\n", (x))

int main(void)
{
	int x = 1;
	NUM(x);
	NUM(2+4);
	
	return 0;
}

其运行结果为:
x is 2.
2+4 is 6.
##运算符可用于类函数宏的替换部分,多说无益,我们来看个例子:

#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);

int main(void)
{
	int XNAME(1) = 114;      //此处就像当于int x1 = 14;  将XNAME(1)宏展开为x1
	 PRINT_XN(1);       //变成printf("x1 = %d\n", x1);
	 return 0;
} 

变参宏:…和__VA_ARGS__
通过把宏参数列表中最后的参数写成…来实现,预定义宏__VA_ARGS__就可用在替换部分中,例如(假设age已有定义和赋值):

#define PRINT(...) printf(__VA_ARGS__)
PRINT("hello world\n");
PRINT("I am %d years old\n", age);

该代码相当于
printf(“hello world\n”);
printf(“I am %d years old\n”, age);
不过值得注意的是省略号只能代替最后的宏参数,一下定义是错误的

#define ERR(..., X) #X #__VA_ARGS__   //这样是错误的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值