我知道在C / C ++中极其不安全地实现它们。他们不能以更安全的方式实施吗?宏的缺点真的严重到足以超过它们提供的强大功能吗?
WithEvents
修饰符相对应的功能?您需要像语义宏之类的东西。
我知道在C / C ++中极其不安全地实现它们。他们不能以更安全的方式实施吗?宏的缺点真的严重到足以超过它们提供的强大功能吗?
WithEvents
修饰符相对应的功能?您需要像语义宏之类的东西。
Answers:
我认为主要原因是宏是词法的。这有几个后果:
编译器无法检查宏是否在语义上是关闭的,即,它像函数一样代表“含义单元”。(考虑#define TWO 1+1
- TWO*TWO
等于多少?3.)
宏的输入方式与函数不同。编译器无法检查参数和返回类型是否有意义。它只能检查使用宏的扩展表达式。
如果代码无法编译,则编译器无法知道错误是在宏本身中还是在使用宏的地方。编译器要么在一半时间报告错误的位置,要么必须报告两者,即使其中之一可能很好。(考虑#define min(x,y) (((x)<(y))?(x):(y))
:如果x
和y
不匹配,或者没有实现operator<
?)
自动化工具无法以语义上有用的方式使用它们。尤其是,对于像宏一样工作的函数,您不能拥有像IntelliSense这样的东西,但可以扩展为表达式。(同样是min
示例。)
宏的副作用不像函数那样明显,这可能会引起程序员的困惑。(再次考虑min
示例:在函数调用中,您知道x
只被计算一次,但是在这里,如果不查看宏就无法知道。)
就像我说的,这些都是宏是词法事实的结果。当您尝试将它们变成更合适的东西时,最终会得到函数和常量。
但是是的,宏可以进行设计,比C / C ++更好的实现。
宏的问题在于它们实际上是一种语言语法扩展机制,可以将您的代码重写为其他形式。
在C / C ++情况下,没有基本的健全性检查。如果小心,一切都会好起来的。如果您犯了一个错误,或者您过度使用了宏,则可能会遇到大问题。
除此之外,您可以使用(C / C ++样式)宏执行许多简单操作,而这些操作可以通过其他语言以其他方式完成。
在诸如各种Lisp方言之类的其他语言中,宏可以更好地与核心语言语法集成在一起,但是您仍然可以在宏“泄漏”中进行声明时遇到问题。这可以通过卫生宏解决。
宏(宏指令的缩写)首先出现在汇编语言的上下文中。根据Wikipedia的说法,1950年代某些IBM汇编器中提供了宏。
最初的LISP没有宏,但是它们最早是在1960年代中期引入MacLisp的:https : //stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code -transformation-appear。http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf。在此之前,“ fexprs”提供了类似于宏的功能。
最早的C版本没有宏(http://cm.bell-labs.com/cm/cs/who/dmr/chist.html)。这些是通过预处理器在1972-73年左右添加的。在此之前,C仅支持#include
和#define
。
M4宏预处理器起源于1977年左右。
显然,较新的语言在操作模型是句法而非文本的地方实现了宏。
因此,当有人谈论术语“宏”的特定定义的首要性时,重要的是要注意该含义已经随着时间而演变。
正如Scott所指出的,宏可以让您隐藏逻辑。当然,函数,类,库和许多其他常见设备也是如此。
但是强大的宏系统可以走得更远,使您能够设计和利用通常在该语言中找不到的语法和结构。实际上,这可能是一个很棒的工具:特定领域的语言,代码生成器等等,所有这些都可以在一种语言环境下轻松实现。
但是,它可能会被滥用。它会使代码更难于阅读,理解和调试,增加新程序员熟悉代码库所需的时间,并导致代价高昂的错误和延迟。
因此,对于旨在简化编程的语言(例如Java或Python),这样的系统简直是天灾人祸。
在某些情况下,可以非常安全地实现宏-例如,在Lisp中,宏只是将转换后的代码作为数据结构(s表达式)返回的函数。当然,Lisp的优点在于它具有同调性和“代码就是数据”这一事实。
这个Clojure示例是一个简单的宏示例,它指定在异常情况下要使用的默认值:
(defmacro on-error [default-value code]
`(try ~code (catch Exception ~'e ~default-value)))
(on-error 0 (+ nil nil)) ;; would normally throw NullPointerException
=> 0 ;l; but we get the default value
即使在Lisps中,一般建议是“除非必须,否则不要使用宏”。
如果您不使用谐音语言,则宏会变得更加棘手,并且其他各种选项都有一些陷阱:
此外,宏可以做的所有事情最终都可以以某种完整的语言以其他方式实现(即使这意味着要编写很多样板)。由于所有这些棘手的结果,因此许多语言都认为宏并不真正值得花所有的精力来实现,这并不奇怪。
要回答您的问题,请考虑主要用于什么宏(警告:大脑编译的代码)。
#define X 100
可以很容易地替换为: const int X = 100;
#define max(X,Y) (X>Y?X:Y)
在任何支持函数重载的语言中,都可以通过具有正确类型的重载函数,或者以支持泛型的语言,通过泛型函数,以更加类型安全的方式进行模拟。该宏将很乐意尝试比较可能编译的任何内容,包括指针或字符串,但几乎可以肯定不是您想要的。另一方面,如果使宏成为类型安全的宏,则与重载函数相比,它们没有任何好处或便利。
#define p printf
这很容易被功能p()
相同的函数所替代。这在C中很复杂(要求您使用va_arg()
函数族),但是在许多其他支持可变数量的函数参数的语言中,它要简单得多。
支持这些功能中的一种语言,而不是一个特殊的宏语言更简单,更容易出错,远不如混乱给他人阅读的代码。实际上,我无法想到无法以其他方式轻松复制的宏的单个用例。宏真正有用的唯一地方是当它们与条件编译结构#if
(如等)绑定在一起时。
关于这一点,我不会与您争论,因为我认为流行语言中的条件编译的非预处理器解决方案非常麻烦(例如Java中的字节码注入)。但是,像D这样的语言提出了不需要预处理器的解决方案,并且比使用预处理器条件更麻烦,同时不容易出错。
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way
:至少在C语言中,如果不使用宏,就不能使用令牌级联来形成标识符(变量名)。
enum
来定义条件并使用常量字符串数组来定义消息可能会导致问题。枚举和数组不同步。使用宏定义所有(枚举,字符串)对,然后每次两次将该宏与范围内的其他正确定义一起使用,将使每个枚举值都放在其字符串旁边。
我在宏中看到的最大问题是,当宏大量使用时,它们会使代码难以阅读和维护,因为它们使您可以在宏中隐藏逻辑,这些逻辑可能难于查找,也可能难于查找。 )。