第六章 预处理器

第六章 预处理器

《C陷阱与缺陷》学习笔记posts

导读

预处理器使得编程者可以简化某些工作,它的重要性可以由两个主要的原因说明。

  • 第一,有时候我们希望将某个特定数量(如数据表的大小)在程序中出现的所有实例统统加以修改。只要改一个地方,其他所有地方都修改。
  • 第二,C语言函数调用时都会带来巨大的系统开销。因此,我们希望有这样一种程序块,它看上去像一个函数,但却没有函数调用。比如,getcharputchar经常被实现为宏,

宏的作用非常强大,有时候可以使代码看起来更加容易理解,将数字或符号替换为自己熟悉的命名。

6.1 不能忽视宏定义中的空格

宏也是有参数的,和函数一样。函数有空格无关紧要,但是如果宏定义带了空格意思就不一样了。 比如

1.#define f (x) ((x) - 1)

可能观察不仔细的人会以为是将f(x)替换成((x) - 1),这样编译通过不了!所以必须要像下面这样写:

1.#define f(x) ((x) - 1)

这一规则不适用与宏调用,只对宏定义有用。因此,在上面完成宏定义后,f(3)与f (3)求值后都等于2。

6.2 宏并不是函数

6.2.1 括号预防优先级问题

宏严格来说并不是函数,但是很多程序员都喜欢把一些简单的函数定义为宏。比如如下写法:

1.#define abs(x) (((x) >= 0) ? (x) : -(x))
2.#define max(a,b) ((a) > (b) ? (a) : (b))

之所以这么多括号是预防引起优先级有关的问题,如果没有括号可能会造成优先级的问题。因为宏不像函数,宏只是起到了一个替换的作用,直接将define的中间替换为后面的表达式。比如

1.#define abs(x) x>0?x:-x
2.abs(a-b);       //

会被展开为 a-b>0?a-b:-a-b,其中的-a-b相当于(-a)-b,而不是想象中的-(a-b)。所以最好用括号括起来。

6.2.2 避免参数副作用

在用到类似的三元运算符宏定义#define max(a,b) a>b ? a : b时,因避免在三元运算符里对数进行改变而产生副作用。比如:

1.biggest = ((biggest) > (x[i++]) > (biggest) : (x[i++]));

在上述表达式中,若i0,那么biggest会与x[0]先比较,然后因为i++的作用,此时i为1,然后再在后面的赋值中又有i++,此时计算完毕i的值已经为2,而不是预料之中的1。 所以要确保max中的参数没有副作用

6.3 宏并不是语句

有些人会认为宏也是语句,如果单独使用也和语句一样,有分号作终止符,其实大错特错! 比如assert宏,它的参数是一个表达式,如果该表达式为0,就使程序终止执行并给出一条错误信息。

1.#define assert(e) if(!e) assert_error(_FILE,_LINE_)
2.
3.if(x > 0 && y > 0)
4.    assert(x > y);
5.else
6.    assert(y > x);

上面的式子通过宏替换后就是这样样子:

1.if( x > 0 && y > 0)
2.    if(!(x > y)) assert_error("foo.c",37);
3.else
4.    if(!(y > x)) assert_error("foo.c",39);

请注意之前的if-else结合优先级问题,未匹配的else会与其最近的if结合。将代码适当缩排一下:

1.if( x > 0 && y > 0)
2.    if(!(x > y)) 
3.        assert_error("foo.c",37);
4.    else
5.        if(!(y > x)) 
6.            assert_error("foo.c",39);

可以看到实际流程与我们想象中的有所出入,那么如何解决这个问题? 这样定义assert:

1.#define assert(e) ((void)((e))||_assert_error(__FILE__,__LINE__)))

这个定义实际上利用了||运算符对两侧的操作数依次顺序求值的性质。具体的也不是很理解。

宏并不是类型定义

宏的一个常见用途是,使多个不同变量的类型可在一个地方说明:

1.#define FOOTYPE struct foo
2.FOOTYPE a;
3.FOOTYPE b,c;

这样,编程者只需在程序中改动一行代码,即可改变a、b、c的类型,而与a、b、c在程序中的什么地方声明无关。这种用法有一个优点——可移植性。但是我们最好还是使用类型定义:

1.typedef struct foo FOOTYPE;

看起来差不多,但是使用起来就会有很大的差别。例如,如下代码:

1.#define T1 struct foo *
2.typedef struct foo  *T2;

从上面看,T1T2好像完全一样,都是指向结构foo的指针。但是当我们试图用他们来声明多个变量时,问题就来了。

1.T1 a, b;
2.T2 a, b;
3.//第一个声明被扩展为:
4.struct foo *a, b;

这个语句中a被定义为一个指向结构的指针,而b却被定义为一个结构。第二个声明则不同,它定义的都是指向结构的指针。

总结

宏是一个强大的功能,用好了则事半功倍,否则事倍功半!总结以下几点,写代码的时候要十分注意。

  • 宏不要随便加空格,因为define后面的两个式子就是相互替换的关系,如果多了一个空格那么就会变成三个式子;
  • 宏不是函数,是直接替换内容,要考虑运算符优先级的问题,必要时应该加上空格防止优先级错误;
  • 宏要避免参数副作用,不要在宏里进行多余的操作,比如++操作;
  • 宏不是语句,不要想当然的当成语句来处理,需要加分号等结束符;
  • 宏不适用于类型定义,多重定义的时候会出现歧义。



查看原文:http://tanwenbo.top/c/%e7%ac%ac%e5%85%ad%e7%ab%a0-%e9%a2%84%e5%a4%84%e7%90%86%e5%99%a8.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值