运行的程序并不是我们所写的程序:因为 C 预处理器首先对其进行了转换。出于两个主要原因(和很多次要原因),预处理器为我们提供了一些简化的途径。

  1. 我们希望可以通过改变一个数字并重新编译程序来改变一个特殊量(如表的大小)。
#define N (1024)
int array[N];
  1. 我们可能希望定义一些东西,它们看起来象函数但没有函数调用所需的运行开销。例如,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 将被求值两次:第一次是在 ab 比较期间,第二次是在计算 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;

这样,编程者只需在程序中改动一行代码,即可改变 abc 的类型,而与 abc 在程序中的什么地方声明无关。
宏定义的这种用法有一个优点——可移植性,得到了所有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却被定义为一个结构体(不是指针)。第二个声明则不同,它定义了 ab 都是指向结构体的指针,因为这里 T2 的行为完全与一个真实的类型相同。

#define可能是个“渣男”相关推荐

  1. 匈牙利算法——海王们的渣男渣女行为

    二分图定义 二分图又称作二部图,是图论中的一种特殊模型. 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不 ...

  2. 哈工大导师禁止实验室硕士出去实习,称「实习就像和35岁渣男试婚」,你怎么看?...

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文授权转载自募格学术(ID:mugexueshu) 参考资料:知乎 ...

  3. 点标记 高德地图_打尽渣男渣女的查岗神器?高德家人地图实测

    从「高考防堵」攻略到「积水提醒」功能,再到「实时对讲」导航,高德地图在7月份接连推出了好几个新功能.其中备受关注的,莫过于上周"低调"上线「家人地图」功能.一上线微博又双叒炸开了锅 ...

  4. 渣男一般的产品经理长什么样?管过几百产品之后,谈谈如何做好这一行

    PMCAFF(www.pmcaff.com):互联网产品社区,是百度,腾讯,阿里等产品经理的学习交流平台.定期出品深度产品观察,互联产品研究首选. 李明升:前微博初创团队产品负责人. 序:做一个优秀的 ...

  5. 【机器学习】情侣、基友、渣男和狗-基于时空关联规则的影子账户挖掘

    故事从校园一卡通开始,校园一卡通是集身份认证.金融消费.数据共享等多项功能于一体的信息集成系统,也就是学生卡.积累了大量的历史记录,其中蕴含着学生的消费行为和财务状况等信息.是一个数据分析比赛的数据, ...

  6. 世纪渣男何书桓! | 今日最佳

    全世界只有3.14 % 的人关注了 青少年数学之旅 (图源网络,侵权删) 渣男,实锤! ↓ ↓ ↓

  7. 为什么有些女孩在发现渣男的真面目以后,还喜欢他们?

    社会中偶尔会爆出XX女孩被渣的新闻,比如直播时被前夫烧伤致死的四川金川网红拉姆,她明知道前夫是个渣男,还一次次选择原谅,到底是为啥呢?今天就来剖析女孩没离开渣男的背后原因. 1.低自尊 低自尊的外在表 ...

  8. 有时间同情渣男傻女,不如想想人都是怎么被臆想出来的爱情给坑了

    第184原创 文|明玥 1 整个周末,整个互联网社交环境都被某大叔男明星的丑闻弄得乌烟瘴气. 这么恶心的瓜,我原本并不想吃. 因为细究一下,你就会发现,这个里面没有谁是值得站的,每个人都经不起推敲. ...

  9. 为了多拿点补贴,马斯克甚至还当过“渣男”?

    来源| 差评 ID| chaping321 作者| 差评君 作为这颗行星上目前最有钱的人,马斯克身上的标签,可以说是贴都贴不完. 成功的企业家.硅谷钢铁侠这些大家熟悉的身份咱们就先不说了. 光是今年喊 ...

最新文章

  1. java gmail smtp_通过JAVA中的Gmail SMTP服务器发送电子邮件
  2. c++ boost库
  3. SAP Leonardo及客户案例
  4. _reincarnation
  5. eclipse打包成jar_Spring Boot 打包成的可执行 jar ,为什么不能被其他项目依赖?
  6. 简述ajax的优缺点
  7. 【C语言】找到兼职了心情紧张!
  8. 小鹏汽车高管个人年薪超4亿?网友:超过我对金钱的认知了
  9. 时间戳服务器显示invalid,signtool签名时间戳失败的解决方法
  10. python关键词共现_python 共现矩阵的实现
  11. AjaxControlToolKit(整理)三.......(35个控件)简单介绍
  12. tensorflow学习笔记(3)梯度下降法进行曲线拟合和线性回归
  13. excel正在等待某个应用程序以完成对象链接与嵌入操作_ES32 嵌入式开发从这里开始...
  14. Charles mac版本进行https抓包的配置方法
  15. 常见设计模式之(五):观察者模式
  16. 20个vue开源项目免费模板源码
  17. 计算机思维典型方法有抽象,传说中的四大编程思维 一篇彻底搞清楚
  18. 我的生活与程序员职业规划
  19. 2021文都最新数学考研讲义(数学一、数学二、数学三)
  20. android万能驱动制作方法

热门文章

  1. 善于进步的人善于让步
  2. Spring Cloud Gateway配置熔断CircuitBreaker
  3. 关于工资结算的C语言程序,C语言程序设计,纳税工资系统
  4. 2022年登高架设考试题模拟考试题库及模拟考试
  5. 栅格数据的三种存储格式--BSQ、BIL、BIP
  6. 【算法】妙不可言---算法复杂性
  7. 阿里云服务器怎么样可以安装Clamav免费杀毒工具
  8. c语言中字符串输入格式错误的是什么,C语言中scanf函数格式化错误输入问题
  9. 张量分解和应用(1)
  10. CT医学影像的窗高窗位、CT值(Hu值)