文章目录
  1. 1. 基本原理
  2. 2. 文本宏
  3. 3. 语法宏
  4. 4. 使用场景

对于一门语言来说,它所能处理的形式和结构总是固定的。有时,我们会尝试增加语言的抽象能力:将输出文本进行纯粹的文本转换,再交由该语言的编译器或者解释器解析。

宏有助于定义这种转换,无论是纯文本形式,抑或是语法宏—需要理解底层语言语法。

基本原理

在程序设计语言构建抽象方面,宏是最古老的技术之一。

宏主要有两类:文本宏和语法宏。文本宏更为人熟悉,易于理解–它们就是把文本当做文本。语法宏则理解宿主语言的语法结构。

文本宏

宏处理最简单的形式是,用一个字符串替换另一个。比如CSS中的颜色代码,多次重复可以考虑使用文本宏代码,便于维护。

文本宏更有趣的语法是参数化。

#define max(x,y) x>y?x:y

宏和调用的区别是,宏是在预编译期执行的。它所做的就是,对max表达式进行文本的搜索替换,替换掉相应的实参,对编译器而言,max是不存在的。

所以,宏省去了函数调用的开销。但是宏也有麻烦,总会有一些微妙的问题,特别是使用参数时,如

#define sqr(x) x*x

如果计算sqr(a+b) ,则展开是a + a*b + b;要避免这种情况,要使用较多圆括号。

#define sqr(x) ((x)*(x))

另外,对于上面的max,如果x 取值 ++a,则就会多计算一次。

还有变量捕获问题:

#define cappedTotal(input, cap, result) \
{int i, total = 0; \
for(i=0; i < 5; i ++) \
total = total + input[i];\
result = (total > cap) ? cap : total;}

如果如下调用

int arr1[5] = {1,2,3,4,5};
int amount = 0;
cappedTotal (arr1, 10, amount);

可以正常工作,但是改成如下代码:

int total = 0;
cappedTotal (arr1, 10, total);

代码执行之后,total为0。问题在于,total这个名字在宏里展开了,但是展开时,宏认为它是红定义本身的变量,所以,传给宏的变量忽略了–这种错误成为变量捕获。

语法宏

宏处理在多数编程环境中都已风光不再,但是有两种语言则是明显的例外:C++和Lisp。

C++中,语法宏就是模版。

Lisp中很多核心功能都是通过宏完成的,多数甚至绝大多数宏,都是在处理闭包时打磨语法。

Lisp中宏常见问题:变量捕获、多次执行。

使用场景

文本宏初看很吸引人,任何使用文本的语言都可以使用,编译时就可以完成所有的错,甚至可以实现一些超越宿主语言能力的很炫的行为。但是,它问题很多,像错误展开、变量捕获、多次执行等诡异的bug时有发生,且极难追踪,所以不推荐使用文本宏。

语法宏的大多数都适用。然而多数语言不支持,所以是否使用语法宏,主要取决于语言环境。

文章目录
  1. 1. 基本原理
  2. 2. 文本宏
  3. 3. 语法宏
  4. 4. 使用场景