我不太了解宏观概念。什么是宏?我不明白它与功能有何不同?函数和宏均包含代码块。那么宏和函数有何不同?
我不太了解宏观概念。什么是宏?我不明白它与功能有何不同?函数和宏均包含代码块。那么宏和函数有何不同?
Answers:
在观察这个答案的两极分化的投票方式之后,我想补充以下澄清。
答案的撰写并未牢记技术准确性和广泛的概括性。这是一个谦虚的尝试,用简单的语言向编程新手解释宏和函数之间的差异,而又不试图做到完整或准确(实际上,它远非准确)。起草答案时,我想到的是C编程语言,但很抱歉,我没有明确提及它并引起任何(潜在的)混乱。
我真的很喜欢JörgW Mittag的答案。阅读它是很有见地的(我所知不多),并且在它发布后不久就对其进行了投票。我刚开始软件工程堆栈交换,到目前为止的经验和讨论都非常有见地。
我将在这里留下这个答案,因为它可能对其他软件开发新手有所帮助,他们试图理解一个概念而不会陷入技术上的准确性。
宏和函数均表示代码的独立单元。它们都是辅助程序模块化设计的工具。从编写源代码的程序员的角度来看,它们看起来非常相似。但是,它们在程序执行生命周期中的处理方式不同。
宏只定义一次,并在程序中的许多地方使用。宏在预处理阶段内联扩展。因此,一旦源代码被编译,它在技术上就不会保留为单独的实体。与其他语句一样,宏定义中的语句也成为程序指令的一部分。
编写宏的动机是使程序员更容易编写和管理源代码。宏通常用于较简单的任务,在这些任务中,编写完整的功能可能会降低性能/运行时间。宏胜于功能的情况示例包括:
使用常数值(例如数学或科学值)或某些程序特定的参数。
打印日志消息或处理断言。
执行简单的计算或条件检查。
使用宏时,很容易在一个地方进行更改/更正,该宏可以在程序中使用宏的任何地方立即获得。要使更改生效,需要对程序进行简单的重新编译。
另一方面,功能代码被编译为程序中的单独单元,并且仅在需要时才在程序执行期间加载到内存中。功能代码保留了程序其余部分的独立身份。如果多次调用该函数,则已加载的代码将被重用。当正在运行的程序中遇到函数调用时,运行时子系统会将控制权传递给该函数,并保留正在运行的程序的上下文(返回指令地址)。
但是,调用函数时会遇到轻微的性能损失(上下文切换,保留主程序指令的返回地址,传递参数和处理返回值等)。因此,仅对于复杂的代码块(针对处理更简单情况的宏)才需要使用函数。
凭经验,程序员会做出明智的决定,即一段代码是否适合作为宏或整个程序体系结构中的函数。
不幸的是,在编程中术语“宏”有多种不同的用法。
在Lisp语言族以及受其启发的语言中,以及许多现代功能或受功能启发的语言(例如Scala和Haskell)以及某些命令性语言(例如Boo)中,宏是一段在编译时运行的代码(或至少在运行时之前没有编译器的实现),并且可以在编译期间将抽象语法树(或特定语言中的任何等效内容,例如Lisp中的S-Expressions)转换为其他内容。例如,在许多Scheme实现中,for
是一个宏,它扩展为对主体的多个调用。在静态类型的语言中,宏通常是类型安全的,即,它们不能生成类型不正确的代码。
在C语言家族中,宏更像是文本替换。这也意味着他们可能会产生格式错误的代码,甚至语法上不合法的代码。
在宏汇编器中,“宏”指的是“虚拟指令”,即CPU本身不支持但有用的指令,因此汇编器允许您使用这些指令并将其扩展为CPU可以理解的多个指令。
在应用程序脚本编制中,“宏”是指用户可以“记录”和“回放”的一系列动作。
从某种意义上说,所有这些都是可执行代码,这意味着它们在某种意义上可以看作是函数。但是,例如,对于Lisp宏,其输入和输出是程序片段。对于C,它们的输入和输出是令牌。前三个还具有非常重要的区别,它们是在编译时执行的。实际上,顾名思义,C预处理程序宏实际上是在代码甚至到达编译器之前执行的。
在C语言家族中,宏定义(预处理器命令)指定参数化的代码模板,该模板在宏调用时替换而无需在定义中进行编译。这意味着所有自由变量都应在宏调用的上下文中进行绑定。i++
通过多次使用参数,可以重复使用具有副作用的参数参数。1 + 2
某些参数的参数的文本替换x
发生在编译之前,并且可能导致意外的行为x * 3
(7
io 9
)。宏定义的宏主体中的错误将仅在宏调用时进行编译时显示。
该函数定义指定代码与结合到功能体的上下文中自由变量; 不是函数调用。
但是,看起来像是负数的宏提供了对调用,行号和源文件以及作为string的参数的访问。
用稍微抽象些的术语来说,宏是语法,而函数是数据。一个函数(摘要)封装了对数据的一些转换。它以其参数作为评估数据,对其进行一些操作,并返回一个结果,该结果也只是数据。
相反,宏采用一些未评估的语法并对其进行操作。对于类似C的语言,语法位于令牌级别。对于具有类似LISP宏的语言,它们获得的语法表示为AST。宏必须返回新的语法块。
一个宏观一般是指东西是在地方扩张,编译或在目标语言单个指令前处理过程中更换的宏“通话”。在运行时,通常不会指示宏的开始和结束位置。
这与子例程不同,子例程是可重用的代码段,它们分别位于内存中,在运行时将控制传递给该子例程。大多数编程语言中的“函数”,“过程”和“方法”都属于此类。
正如JörgW Mittag的答案所讨论的,确切的细节因语言而异:在某些语言(例如C)中,宏在源代码中执行文本替换;在某些语言中,宏执行文本替换。在某些语言(例如Lisp)中,它执行诸如抽象语法树之类的中间形式的操作。也有一些灰色区域:有些语言对“内联函数”有表示法,它们像函数一样定义,但像宏一样扩展到已编译的程序中。
大多数语言都鼓励程序员独立思考每个子例程,为输入和输出定义类型协定,并隐藏有关调用代码的其他信息。调用宏不会引起的子例程通常会对性能产生影响,并且宏可能能够以子例程无法执行的方式来操纵程序。因此,与子例程相比,宏可以被认为是“较低级别的”,因为它们的运行方式不太抽象。
宏在编译期间执行,函数在运行时执行。
例:
#include <stdio.h>
#define macro_sum(x,y) (x+y)
int func_sum(x,y) {
return x+y;
}
int main(void) {
printf("%d\n", macro_sum(2,3));
printf("%d\n", func_sum(2,3));
return 0;
}
因此,在编译过程中,代码实际上更改为:
#include <stdio.h>
int func_sum(x,y) {
return x+y;
}
int main(void) {
printf("%d\n", (2+3));
printf("%d\n", func_sum(2,3));
return 0;
}