C语言宏定义精析

来源:互联网 发布:护理网络教育本科 编辑:程序博客网 时间:2024/05/04 00:37

C语言的学习中对于经常使用的常量我们经常使用宏定义,最常用的是#define PI 3.1415926 以后在程序中遇到PI编译器自动将PI替换成3.1415926,非常的方便,因此都很喜欢采用宏定义。宏定义具有简单方便已于修改的特点,但是并不是宏定义没有缺点,因为宏定义采用的是原地替换的策略,因此对于大型程序的调试带来很大不便。因此在C++中便提倡采用const去替换宏,然而宏定义作为C语言一个重要的知识点还是必须掌握的。下面就简单介绍宏定义的几种常用的用法。

平时我们解除最多的就是#define XXX xxx的形式,例如

#define MAXSIZE 100

……

For(i=0;i<MAXSIZE;i++)

对于宏定义的这种用法就不多介绍了。

可能大家见到过带参数的宏定义,这种在分析Linux内核的时候经常看到,那么这种形式的宏定义是怎样实现呢?很多人见到带参数的宏定义直接就懵了,其实对宏定义的分析只需要把握住宏定义的本质即可:宏定义是原地展开直接替换。遇到宏定义系统直接替换,不管宏定义是什么形式!因此这是宏定义的弊端之一:编译器并不检查宏定义的内容是否合法,因此并不能保证程序的正确,因此采用宏定义时必须确保宏定义内容的合法性。注意:不是语法的正确性,因为宏定义出现语法错误编译器是可以知道的。

下面研究带参宏定义,看下面一段代码:

#define P 3

#define F(x) P*x*x

int main()

{

    printf("%d\n",F(8));

    return 0;
    }

对于这个程序P3进行替换,当程序遇到F(8)时,解析出格式Fx)因此x8替换,则printf语句相当于printf("%d\n",3*8*8);.

经过上面的介绍大家应该对带参数宏定义有了初步的印象,下面继续介绍。

如果将上面语句改成printf("%d\n",F(3+5));结果是不是不变呢?这时候F(x)替换的形式是3*3+5*3+5)?当然不是!!!原因依旧是宏定义的本质!采用原地展开替换,因此编译器会将#define F(x) P*x*x中的x3+5替换即:3*3+5*3+5.因此当采用带参宏定义时为了避免上述的错误应对宏替换的部分加上括号,即:P*(x)*(x).从上面可以看出,如果宏定义形式不当会导致错误结果,然而在调试时,却无法看见Fx)形式,因为宏定义在预编译时已经替换!所以会给调试带来困难,这也是C++一直避免使用宏定义的原因。

从上面可以看出宏定义可以是一个带参的函数,那么这样的宏定义与带参函数有什么区别呢?在C语言学习函数调用时,我们经常会采用下面例子

void swap(int a,int b)

{

    a+=b;

    b=a-b;

    a-=b;
    }

int main()

{

    swap(m,n);

    Return 0;
    }

对于这样的调用我们知道这并不能真正的交换这两个数的值,因为交换函数在生存周期结束后会将空间收回,因此并不能真正的交换两个数的值。(插播一句:上面的交换两个数的方法并不需要额外的存储空间因此是一钟很好的方法。在对时间和空间的追求中这种方法无疑是很棒的)

然而这种形式对于宏定义是不是一样呢?例如:

#define swap(a,b) a+=b;b=a-b;a-=b

根据宏定义的本质,我们知道当主函数调用swap(m,n)时会自动将a替换成mb替换成n,因此ambn是等价的,因此当ab改变时对应的mn也就自然改变了。这种方式与C++引用调用是一样的。因此我们可以得到:带参数宏定义运算是会影响调用函数的参数值得,这雨C++引用调用相同。

当大家按照上面宏定义去编写程序,进行编译时编译器会显示该行缺少分好!但是很多人就会纳闷了,这到底那里少了?因为学习宏定义时老师说过宏定义后面是没有分号的,难道世界观崩塌了?显然不是,老师说的宏定义后面没有分好是针对#define的然而此时的分好并不属于#defin它只是a-=b语句的结束符!!!因为缺少分好,则替换时会因为最后语句缺少结束符而出错,因此大胆的给后面添加分号吧。世界观还没有崩塌。

经过上面的介绍想必大家对宏定义有了一个基本的了解,下面介绍一种更高级的用法。

看了上面想必大家会想,既然可以定义带参数的宏定义那么可不可以定义参数可变的宏呢?当然可以。一种流行的做法就是加括号,如下:

#define  DEBUG(args)   (printf("DEBUG:   "),  printf  args) 

    if(n !=  0) DEBUG(("n   is %d\n",  n)); 

    但是明显的缺陷是必须记住使用一对额外的括弧。 

gcc 有一个扩展可以让函数式的宏接受可变个数的参数。但这不是标准。另一种可能的解决方案是根据参数个数使用多个宏(DEBUG1, DEBUG2, 等等), 或者用逗号玩个这样的:

#define  DEBUG(args)   (printf("DEBUG:   "),  printf(args)) 

#define  _ , 

DEBUG("i  = %d"  _ i); 

在这样的使用过程中,大家可能会遇到下面问题:

#dene TRACE(n) printf("TRACE: %d\n ", n) 报出警告 “用字符串常量代替宏”

出现这个问题是因为有些ANSI 前的编译器/预处理器把下面这样的宏 

    #define  TRACE(var,  fmt)  printf("TRACE:   var  = fmt\n",   var)解释为TRACE(i,  %d); 这样的调用会被扩展为printf("TRACE:   i = %d\n",  i);换言之字符串常量内部也作了宏参数扩展。标准都没有定义这样的宏扩展。因此希望把宏参数转成字符串时,,可以使用新的预处理操作符和字符串常量连接(ANSI 的另一个新功能): 

#define  TRACE(var,  fmt)  \ printf("TRACE:   "  #var "  = " #fmt  "\n",  var) 

原创粉丝点击