按姓名致电:
按名称调用是一种评估策略,其中在调用函数之前不评估函数的参数,而是将其直接替换为函数体(使用捕获避免替换),然后在它们出现在函数中时立即进行评估。功能。如果函数主体中未使用参数,则永远不会对参数求值;如果多次使用,则每次出现时都会对其进行重新评估。(请参阅詹森的装置。)
按名称进行呼叫评估有时要优于按值进行呼叫评估。如果函数中未使用函数的参数,则按名称调用将不评估参数而节省时间,而按值调用将对其进行评估。如果自变量是不间断的计算,则好处是巨大的。但是,使用function参数时,按名称调用通常较慢,这需要诸如thunk之类的机制。
早期使用的是ALGOL60。当今的.NET语言可以使用委托或Expression参数按名称模拟调用。后者导致将抽象语法树提供给该函数。埃菲尔提供代理,代表需要时要评估的操作。Seed7通过名称提供带有函数参数的调用。
宏调用:
按宏扩展调用类似于按名称调用,但是使用文本替换而不是避免捕获的替换。如果使用不谨慎,宏替换可能会导致变量捕获并导致不良行为。卫生宏通过检查和替换不是参数的阴影变量来避免此问题。
注意:使用非严格的评估语言
宏调用示例:
通过宏扩展调用:包括C,lisp和scheme在内的许多编程语言为开发人员提供了一种将新语法添加到称为宏的核心语言语法中的机制。宏预处理器将宏扩展为代码。这些宏可能包含参数,这些参数将复制到预处理器生成的最终代码中。例如,下面的C程序通过宏实现交换功能:
#define SWAP(X,Y) {int temp=X; X=Y; Y=temp;} int main() { int a = 2; int b = 3; printf("%d, %d\n", a, b); SWAP(a, b); printf("%d,
> %d\n", a, b); }
Run Code Online (Sandbox Code Playgroud)
该宏实现有效的交换例程。的
预处理程序看起来像下面的代码。由于宏的主体直接复制到调用程序的文本中,因此它将在该程序的上下文中运行。换句话说,宏将直接引用其接收的变量名称,而不是其值。
int main() { int a = 2; int b = 3; printf("%d, %d\n", a, b); {
> int tmp = (a); (a) = (b); (b) = tmp; }; printf("%d, %d\n", a, b); }
Run Code Online (Sandbox Code Playgroud)
每次在宏主体中使用表达式时,都会对作为参数传递给宏的表达式进行求值。如果从未使用过该参数,则不会对其进行评估。例如,下面的程序将变量b增加两次:
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) int main() { int a = 2, b = 3; int c = MAX(a, b++); printf("a = %d, b = %d, c = %d\n", a, b, c); }宏存在一个问题,称为变量捕获。如果宏定义了在调用方的环境中已经定义的变量v,并且v作为参数传递给了宏,则宏的主体将无法区分v的出现与另一个。例如,下面的程序具有定义变量temp的宏。main内部的调用使该函数内部定义的变量temp被宏体内的定义捕获。
#define SWAP(X,Y) {int temp=X; X=Y; Y=temp;} int main() { int a = 2; int temp = 17; printf("%d, temp = %d\n", a, temp); SWAP(a, temp);
> printf("%d, temp = %d\n", a, temp); }
Run Code Online (Sandbox Code Playgroud)
一旦这个程序被扩展
C预处理程序,我们得到下面的代码。该程序无法交换变量temp和a的值:
int main() { int a = 2; int temp = 17; printf("%d, temp = %d\n",
> a, temp); {int temp=a; a=temp; temp=temp;}; printf("%d, temp =
> %d\n", a, temp); }
Run Code Online (Sandbox Code Playgroud)
有许多懒惰的评估策略
避免了变量捕获问题。两种最知名的技术是按名称呼叫和按需求呼叫。
示例名称呼叫:
按名称调用:在此评估策略中,仅在函数内部使用时才评估实际参数;但是,此评估使用调用程序例程的上下文。例如,在下面的示例中(取自Weber的书),我们有一个函数g返回整数6。在函数f中,第一个赋值(例如b = 5)将5存储在变量i中。第二个赋值b = a,读取i的值(当前为5),并将其加1。然后将该值存储在i处。
Run Code Online (Sandbox Code Playgroud)void f(by-name int a, by-name int b) { b=5; b=a; } int g() { int i = 3; f(i+1,i); return i; }很少有语言实现按名称呼叫评估策略。这些语言中最杰出的是Algol。如本例所示,Algol的直接后代Simula也实现了按名称的调用。即使多次使用此参数,按名称进行的调用也始终会导致评估该参数。在引用透明的语言中,此行为可能是浪费的,因为在这些语言中,变量是不可变的。