#define可能是个“渣男”
运行的程序并不是我们所写的程序:因为 C 预处理器首先对其进行了转换。出于两个主要原因(和很多次要原因),预处理器为我们提供了一些简化的途径。
- 我们希望可以通过改变一个数字并重新编译程序来改变一个特殊量(如表的大小)。
#define N (1024)
int array[N];
- 我们可能希望定义一些东西,它们看起来象函数但没有函数调用所需的运行开销。例如,putchar() 和getchar() 通常实现为宏以避免对每一个字符的输入输出都要进行函数调用。
但是,从另一方面来说。define 存在很多不确定性,导致它在实际应用上出现好多“不靠谱”的情况,而显得“渣”,下面通过一些例子,去看一下这些“不靠谱”的情况。
不能忽视他小小的空格
一个函数如果不带参数,在调用时只需在函数名后加上一对括号即可加以调用了。而一个宏如果不带参数,则只需要使用宏名即可,括号无关紧要。只要宏已经定义过了,就不会带来什么问题:预处理器从宏定义中就可以知道宏调用时是否需要参数。
与宏调用相比,宏定义显得有些”暗藏机关“。
例如,下面的宏定义中 f 是否带了一个参数呢?
#define f (x) ((x)-1)
给两个两个选项:
- ((x)-1)
- (x)((x)-1)
在上述宏定义中,第二选项才是真正展开的内容,这个是编程者希望的样子吗?显然不是,因为在 f 和后面的 (x) 之间多了一个空格!所以,如果希望定义 f(x) 为 ((x)-1) ,必须像下面这样写:
#define f(x) ((x)-1) ///< f 与 ( 之间没有空格)
对于函数定义来说,这样的空格无关紧要,但对于 define 就不是了,或许,嗯,他比较小气吧。
他并非您心里想要的那个他(‘函数’)
由于宏可以象函数那样出现,有些程序员有时就会将它们视为完全等同。因此,我们常常可以看到类似下面的写法:
#define abs(x) (((x)>=0)?(x):-(x))
或者
#define max(a,b) ((a)>(b)?(a):(b))
请注意宏定义中出现的所有这些括号,它们的作用是预防引起与优先级有关的问题。例如,假设宏 abs 被定义成了这个样子:
#define abs(x) x>0?x:-x
让我们来看 abs(a-b) 求值后会得到怎样的结果。表达式
abs(a-b);
会被展开为
a-b>0?a-b:-a-b;
这里的子我达式 -a-b 相当于 (-a)-b ,而不是我们期望的 -(a-b) ,因此上式无疑会得到一个错误的结果。因此,我们最好在宏定义中把每个参数都用括号括起来。
他才不管您的优先级
继续上面的那个例子。
abs (a)+1;
展开后的结果为:
a>0?a:-a+1;
这个表达式很显然是错误的,我们期望得到的是 -a,而不是 -a+1!
那我们如何避免?
应该把 abs 定义
#define abs(x) (((x)>=0)?(x):-(x))
这时
abs (a-b);
才会被正确地展开为:
((a-b)>0?(a-b)(a-b));
而
abs (a) +1;
也会被正确地展开为:
((a)>0?(a)(a))+1;
即使宏定义中的各个参数与整个结果衷达式都被括号括起来,在某些情况下,可能也“无济于事”。
比如说,一个操作数如果在两处被用到,就会被求值两次。例如,在表达式 max(a,b) 中,如果 a 大于 b ,那么 a 将被求值两次:第一次是在 a 与 b 比较期间,第二次是在计算 max 应该得到的结果值时。
这种做法不但效率低下,而且可能是错误的:
biggest = x[0];
i = 1;
while (i < n)biggest = max (biggest, x[i++]);
如果 max 是一个真正的函数,上面的代码可以正常工作;而如果 max 是一个宏,那么就不能正常工作。
然后考察在循环的第一次迭代时会发生什么。上面代码中的赋值语句 biggest = max (biggest, x[i++]) 将被扩展为:
biggest = ((biggest)>(x[i++])?(biggest):(x[i++]));
再举一个例子,它就活生生的在我们身边。
toupper 函数在调用时造成的系统开销要大大多于函数体内的实际计算操作,原系统实现:
int toupper (int c) {if (c >= 'a' && c <= 'z') {c += 'A' - 'a';}}
实现中,使用了大量的条件判断,这样俨然会造成效率的下降,因此,实现者很可能禁不住要把 toupper 实现为宏:
#define toupper(c)\((c)>='a' && (c)<='z'? (c + 'A' - 'a'): (c))
在许多情况下,这样做确实比把 toupper 实现为函数要快得多。然而,如果编程者试图这样使用:
topper(*p++);
结果估计让你“大吃一惊"。
因此谨记,少使用自变表达式作为参数传参(不管是否在 define 定义的函数,除非你比较清除这函数定义机理),如 i++ ,i– 等。
您悬挂它它就撬动您整个项目
编程者有时会试图定义宏的行为与语句类似,但这样做的实际困难往往令人吃惊!
举例来说,设计一个表达式,如果该表达式为 0,就使程序终止执行,并给出一条适当的出错消息。
assert(x>y);
在x大于y时什么也不做,其他情况下则会终止程序。
下面是我们定义 assert 宏的第一次尝试:
#define assert(e) if (!e) assert_error(_FILE_,_LINE_)
因为考虑到宏 assert 的使用者会加上一个分号,所以在宏定义中并没有包括分号。
宏 assert 的这个定义,即使用在一个再明白直接不过的情形中,产生一些难以察觉的错误却能够撬动整个项目的错误:
if (x > 0 && y > 0)assert(x > y);
elseassert(y > x);
上面的写法似乎很合理,但是它展开之后再排一下版就是这个样子:
if (x > 0 && y > 0)if(! (x > y))assert_error("foo-c", 37);elseif ( ! (y > x) )assert_error ("foo. c", 39);
解决这种问题,最有效的方法当然是摒弃掉那种 悬挂式条件 的极度不良的书写习惯,其次,可以通过把宏定义 单语句化,如:
#define assert(e) do { if (!e) assert_error(__FILE__, __LINE__); } while (0)
这样的写法还有一个好出,让该函数定义真正的做到”无返回值“,如果调用者误操作希望把他的返回结果赋值给一个变量:
int r = assert(0);
编译器会毫不犹豫的给我们报错。
别指望他定义类型
宏的一个常见用途是,使多个不同变量的类型可在一个地方说明:
#define FOOTYPE struct foo
FOOTYPE a;
FOOTYPE b,c;
这样,编程者只需在程序中改动一行代码,即可改变 a、b、c 的类型,而与 a、b、c 在程序中的什么地方声明无关。
宏定义的这种用法有一个优点——可移植性,得到了所有C编译器的支持。
但是,我们最好还是使用类型定义:
typedef struct foo FOOTYPE;
这个语句定义了 FOOTYPE 为一个新的类型,struct foo 完全等效。单纯看这个使用似乎与宏定义的使用没有太大区别,但是更多的时候,使用宏定义类型,真的很不靠谱!
例如,考虑下面卽代码:
#define T1 struct foo*
typedef struct foo *T2;
从上面两个定义来看,T1和T2从概念上完全符同,都是指向结构 foo 的指针。但是,当我们试图用它们来声明多个变量时,问题就来了:
T1 a, b;
T2 a, b;
这个语句中 a 被定义为一个指向结构的指针,而b却被定义为一个结构体(不是指针)。第二个声明则不同,它定义了 a 和 b 都是指向结构体的指针,因为这里 T2 的行为完全与一个真实的类型相同。
#define可能是个“渣男”相关推荐
- 匈牙利算法——海王们的渣男渣女行为
二分图定义 二分图又称作二部图,是图论中的一种特殊模型. 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不 ...
- 哈工大导师禁止实验室硕士出去实习,称「实习就像和35岁渣男试婚」,你怎么看?...
点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文授权转载自募格学术(ID:mugexueshu) 参考资料:知乎 ...
- 点标记 高德地图_打尽渣男渣女的查岗神器?高德家人地图实测
从「高考防堵」攻略到「积水提醒」功能,再到「实时对讲」导航,高德地图在7月份接连推出了好几个新功能.其中备受关注的,莫过于上周"低调"上线「家人地图」功能.一上线微博又双叒炸开了锅 ...
- 渣男一般的产品经理长什么样?管过几百产品之后,谈谈如何做好这一行
PMCAFF(www.pmcaff.com):互联网产品社区,是百度,腾讯,阿里等产品经理的学习交流平台.定期出品深度产品观察,互联产品研究首选. 李明升:前微博初创团队产品负责人. 序:做一个优秀的 ...
- 【机器学习】情侣、基友、渣男和狗-基于时空关联规则的影子账户挖掘
故事从校园一卡通开始,校园一卡通是集身份认证.金融消费.数据共享等多项功能于一体的信息集成系统,也就是学生卡.积累了大量的历史记录,其中蕴含着学生的消费行为和财务状况等信息.是一个数据分析比赛的数据, ...
- 世纪渣男何书桓! | 今日最佳
全世界只有3.14 % 的人关注了 青少年数学之旅 (图源网络,侵权删) 渣男,实锤! ↓ ↓ ↓
- 为什么有些女孩在发现渣男的真面目以后,还喜欢他们?
社会中偶尔会爆出XX女孩被渣的新闻,比如直播时被前夫烧伤致死的四川金川网红拉姆,她明知道前夫是个渣男,还一次次选择原谅,到底是为啥呢?今天就来剖析女孩没离开渣男的背后原因. 1.低自尊 低自尊的外在表 ...
- 有时间同情渣男傻女,不如想想人都是怎么被臆想出来的爱情给坑了
第184原创 文|明玥 1 整个周末,整个互联网社交环境都被某大叔男明星的丑闻弄得乌烟瘴气. 这么恶心的瓜,我原本并不想吃. 因为细究一下,你就会发现,这个里面没有谁是值得站的,每个人都经不起推敲. ...
- 为了多拿点补贴,马斯克甚至还当过“渣男”?
来源| 差评 ID| chaping321 作者| 差评君 作为这颗行星上目前最有钱的人,马斯克身上的标签,可以说是贴都贴不完. 成功的企业家.硅谷钢铁侠这些大家熟悉的身份咱们就先不说了. 光是今年喊 ...
最新文章
- java gmail smtp_通过JAVA中的Gmail SMTP服务器发送电子邮件
- c++ boost库
- SAP Leonardo及客户案例
- _reincarnation
- eclipse打包成jar_Spring Boot 打包成的可执行 jar ,为什么不能被其他项目依赖?
- 简述ajax的优缺点
- 【C语言】找到兼职了心情紧张!
- 小鹏汽车高管个人年薪超4亿?网友:超过我对金钱的认知了
- 时间戳服务器显示invalid,signtool签名时间戳失败的解决方法
- python关键词共现_python 共现矩阵的实现
- AjaxControlToolKit(整理)三.......(35个控件)简单介绍
- tensorflow学习笔记(3)梯度下降法进行曲线拟合和线性回归
- excel正在等待某个应用程序以完成对象链接与嵌入操作_ES32 嵌入式开发从这里开始...
- Charles mac版本进行https抓包的配置方法
- 常见设计模式之(五):观察者模式
- 20个vue开源项目免费模板源码
- 计算机思维典型方法有抽象,传说中的四大编程思维 一篇彻底搞清楚
- 我的生活与程序员职业规划
- 2021文都最新数学考研讲义(数学一、数学二、数学三)
- android万能驱动制作方法