函数指针,闭包和Lambda

我现在正在学习函数指针,而当我准备好关于这个主题的K&R章节时,第一个打到我的是“嘿,这有点像闭包。 我知道这个假设在某种程度上是根本错误的,在线search之后并没有真正find对比的分析。

那么为什么C风格函数指针与闭包或lambdas根本不同? 据我所知,与函数指针仍然指向一个已定义的(已命名的)函数,而不是能够匿名定义该函数有关。

为什么要传递一个函数到第二种情况下更强大的函数中,而第二种情况下它是未命名的,而第一种情况只是一个正常的日常函数而已?

请告诉我如何以及为什么我错误地比较两者如此密切。

谢谢。

lambda(或闭包 )封装了函数指针和variables。 这就是为什么在C#中,你可以这样做:

int lessThan = 100; Func<int, bool> lessThanTest = delegate(int i) { return i < lessThan; }; 

我使用了一个匿名委托作为闭包(它的语法比lambda等价,比C更清晰一些),它在闭包中捕获了lessThan(一个堆栈variables)。 当评估闭包时,lessThan(其堆栈框架可能已被销毁)将继续被引用。 如果我改变less了,那么我改变比较:

 int lessThan = 100; Func<int, bool> lessThanTest = delegate(int i) { return i < lessThan; }; lessThanTest(99); // returns true lessThan = 10; lessThanTest(99); // returns false 

在C中,这将是非法的:

 BOOL (*lessThanTest)(int); int lessThan = 100; lessThanTest = &LessThan; BOOL LessThan(int i) { return i < lessThan; // compile error - lessThan is not in scope } 

尽pipe我可以定义一个带有2个参数的函数指针:

 int lessThan = 100; BOOL (*lessThanTest)(int, int); lessThanTest = &LessThan; lessThanTest(99, lessThan); // returns true lessThan = 10; lessThanTest(100, lessThan); // returns false BOOL LessThan(int i, int lessThan) { return i < lessThan; } 

但是,现在我评估它时,必须通过这两个论点。 如果我希望将这个函数指针传递给lessThan不在范围内的另一个函数,我将不得不通过将其传递给链中的每个函数来手动保留它,或者将其提升为全局函数。

尽pipe支持闭包的大多数主stream语言都使用匿名函数,但没有这方面的要求。 您可以closures没有匿名函数,匿名函数没有closures。

总结:闭包是函数指针+捕获variables的组合。

作为一个编写语言编译器的人,不pipe有没有“真正的”closures,我都不同意上面的一些答案。 Lisp,Scheme,ML或Haskell closure 不会dynamic地创build新的函数 。 相反,它重用了一个现有的函数,但用新的自由variables 。 自由variables的集合通常被称为环境 ,至less是编程语言理论家。

闭包只是一个包含函数和环境的聚合。 在新泽西州编译器的标准ML中,我们代表一个logging; 一个字段包含一个指向代码的指针,其他字段包含自由variables的值。 编译器通过分配一个包含指向相同代码的指针的新logging来dynamic创build一个新的闭包(不是函数) ,但是自由variables的值不同

你可以在C中模拟所有这些,但这是一个痛苦的屁股。 两种技术很受欢迎:

  1. 传递一个指向函数的指针(代码)和一个单独的指向自由variables的指针,以便闭包被分成两个Cvariables。

  2. 将一个指针传递给一个结构体,其中结构体包含自由variables的值以及一个指向代码的指针。

当你试图在C中模拟某种多态时,技术#1是理想的,而你不想揭示环境的types—用一个void *指针来表示环境。 例如,看Dave Hanson的C接口和实现 。 技术#2,更类似于用于function语言的本地代码编译器中发生的情况,也类似于另一种熟悉的技术…具有虚拟成员函数的C ++对象。 实现几乎完全相同。

这一观察导致亨利·贝克(Henry Baker)

Algol / Fortran世界的人们抱怨了好几年,他们不明白在未来的高效编程中,封装可能会有什么用处。 然后发生了“面向对象程序devise”的革命,现在每个人都使用函数闭包程序,除了他们仍然拒绝这样的称呼。

在C中,你不能定义函数内联,所以你不能真正创build一个闭包。 你所做的就是传递一个预定义的方法。 在支持匿名方法/闭包的语言中,方法的定义更加灵活。

用最简单的术语来说,函数指针没有与它们相关联的范围(除非你计算全局范围),而闭包包括定义它们的方法的范围。 用lambdas,你可以写一个方法来写一个方法。 闭包允许你将一些参数绑定到一个函数上,并得到一个较低的函数。 (取自托马斯的评论)。 你不能这样做在C.

编辑:添加一个例子(我打算使用Actionscript-ish语法原因,这就是我现在的想法):

假设你有一些方法将另一个方法作为参数,但是在调用时没有提供任何方法将任何parameter passing给该方法。 比如说,在运行传递它的方法之前导致延迟的一些方法(愚蠢的例子,但我想保持简单)。

 function runLater(f:Function):Void { sleep(100); f(); } 

现在说你想用runLater()来延迟一些对象的处理:

 function objectProcessor(o:Object):Void { /* Do something cool with the object! */ } function process(o:Object):Void { runLater(function() { objectProcessor(o); }); } 

你传递给process()的函数不再是一些静态定义的函数。 它是dynamic生成的,并且能够包含对定义方法时在范围内的variables的引用。 所以,它可以访问'o'和'objectProcessor',即使这些不在全局范围内。

我希望这是有道理的。

closures=逻辑+环境。

例如,考虑这个C#3方法:

 public Person FindPerson(IEnumerable<Person> people, string name) { return people.Where(person => person.Name == name); } 

lambdaexpression式不仅封装了逻辑(“比较名称”),还封装了环境,包括参数(即局部variables)“name”。

有关这方面的更多信息,请参阅关于闭包的文章,通过C#1,2和3介绍闭包如何简化操作。

在C中,函数指针可以作为parameter passing给函数,并作为函数的值返回,但是函数只存在于顶层:不能将函数定义嵌套在一起。 考虑一下C会支持可以访问外部函数variables的嵌套函数,同时仍然可以在调用堆栈上下发送函数指针。 (按照这个解释,你应该知道如何用C语言和大多数类似的语言实现函数调用的基本知识:浏览维基百科上的调用堆栈条目。)

什么样的对象是一个嵌套函数的指针? 它不能只是代码的地址,因为如果你调用它,它如何访问外部函数的variables? (请记住,由于recursion,可能会有几个不同的外部函数调用同时激活)。这被称为funarg问题 ,有两个子问题:向下的funargs问题和向上的funargs问题。

向下的funargs问题,即发送一个函数指针“向下堆栈”作为你调用的函数的一个参数,实际上并不与C不兼容,并且GCC 支持嵌套函数作为向下的函数。 在GCC中,当你创build一个指向嵌套函数的指针时,你真的会得到一个指向蹦床的指针,这是一个dynamic构build的代码,它设置了静态链接指针 ,然后调用真正的函数,它使用静态链接指针来访问外部函数的variables。

向上的funargs问题比较困难。 在外层函数不再有效(调用堆栈中没有logging)之后,GCC不会阻止你放置蹦床指针,然后静态链接指针可能指向垃圾。 激活logging不能再分配在堆栈上。 通常的解决scheme是在堆上分配它们,让表示嵌套函数的函数对象指向外部函数的激活logging。 这样的对象被称为封闭 。 然后,语言通常必须支持垃圾回收,这样一旦没有更多的指针指向它们,logging就可以被释放。

Lambdas( 匿名函数 )实际上是一个单独的问题,但是通常可以让你定义匿名函数的语言也可以让你将它们作为函数值返回,所以它们最终会被closures。

lambda是一个匿名的, dynamic定义的函数。 你不能用C来做那个封闭的事情(或者说两者的交汇),典型的lisp例子应该是这样的:

 (defun get-counter (n-start +-number) "Returns a function that returns a number incremented by +-number every time it is called" (lambda () (setf n-start (+ +-number n-start)))) 

在C语言中,可以说get-counter的词法环境(堆栈)被匿名函数捕获,并在内部进行修改,如以下示例所示:

 [1]> (defun get-counter (n-start +-number) "Returns a function that returns a number incremented by +-number every time it is called" (lambda () (setf n-start (+ +-number n-start)))) GET-COUNTER [2]> (defvar x (get-counter 2 3)) X [3]> (funcall x) 5 [4]> (funcall x) 8 [5]> (funcall x) 11 [6]> (funcall x) 14 [7]> (funcall x) 17 [8]> (funcall x) 20 [9]> 

闭包意味着从函数定义的angular度来说,某些variables与函数逻辑绑定在一起,就像能够在运行中声明一个小对象一样。

C和闭包的一个重要问题是,在堆栈上分配的variables在离开当前范围时将被销毁,无论闭包是否指向它们。 这会导致人们不小心返回指向局部variables的指针时出现的错误。 closures基本上意味着所有相关的variables要么被重新计数,要么被垃圾收集到堆中。

我不太确定lambda是否与闭包相等,因为我不确定所有语言中的lambda是否是闭包,有时我认为lambdas刚刚在本地定义了匿名函数而没有绑定variables(Python 2.1之前的版本)。

在GCC中,可以使用下面的macros模拟lambda函数:

 #define lambda(l_ret_type, l_arguments, l_body) \ ({ \ l_ret_type l_anonymous_functions_name l_arguments \ l_body \ &l_anonymous_functions_name; \ }) 

来源示例:

 qsort (array, sizeof (array) / sizeof (array[0]), sizeof (array[0]), lambda (int, (const void *a, const void *b), { dump (); printf ("Comparison %d: %d and %d\n", ++ comparison, *(const int *) a, *(const int *) b); return *(const int *) a - *(const int *) b; })); 

当然,使用这种技术可以消除应用程序与其他编译器一起工作的可能性,并且显然是“未定义的”行为,所以YMMV。

闭包捕获环境中自由variables 。 即使周围的代码可能不再有效,环境仍然存在。

Common Lisp中的一个例子, MAKE-ADDER返回一个新的闭包。

 CL-USER 53 > (defun make-adder (start delta) (lambda () (incf start delta))) MAKE-ADDER CL-USER 54 > (compile *) MAKE-ADDER NIL NIL 

使用以上function:

 CL-USER 55 > (let ((adder1 (make-adder 0 10)) (adder2 (make-adder 17 20))) (print (funcall adder1)) (print (funcall adder1)) (print (funcall adder1)) (print (funcall adder1)) (print (funcall adder2)) (print (funcall adder2)) (print (funcall adder2)) (print (funcall adder1)) (print (funcall adder1)) (describe adder1) (describe adder2) (values)) 10 20 30 40 37 57 77 50 60 #<Closure 1 subfunction of MAKE-ADDER 4060001ED4> is a CLOSURE Function #<Function 1 subfunction of MAKE-ADDER 4060001CAC> Environment #(60 10) #<Closure 1 subfunction of MAKE-ADDER 4060001EFC> is a CLOSURE Function #<Function 1 subfunction of MAKE-ADDER 4060001CAC> Environment #(77 20) 

请注意, DESCRIBE函数显示两个闭包函数对象是相同的,但是环境是不同的。

Common Lisp使得闭包和纯函数对象(没有环境的)都是函数 ,可以用相同的方法调用两者,在这里使用FUNCALL

主要区别在于C中缺less词汇范围

函数指针就是这样一个指向代码块的指针。 它引用的任何非堆栈variables都是全局的,静态的或类似的。

closures,OTOH,以“外部variables”或“upvalues”的forms具有自己的状态。 他们可以像私人或共享,使用词法作用域。 您可以使用相同的函数代码创build大量的闭包,但可以使用不同的variables实例。

一些闭包可以共享一些variables,所以可以是一个对象的接口(在OOP意义上)。 在C语言中,你必须将一个结构与一个函数指针表(这就是C ++所做的,用一个类vtable)相关联。

总之一个封闭的函数指针是PLUS的一些状态。 这是一个更高层次的构造

在C中,函数指针是一个指针,当你对它进行引用时,它将会调用一个函数,闭包是一个包含函数逻辑和环境的variables(variables和它们绑定的值),lambda通常指的是一个值实际上是一个未命名的function。 在C函数不是一个第一类值,所以它不能被传递,所以你必须传递一个指针,但是在函数式语言(如Scheme)中,您可以传递函数的方式与传递任何其他值

大部分的响应表明闭包需要函数指针,可能是匿名函数,但正如Mark写的闭包可以与命名函数一起存在。 下面是Perl中的一个例子:

 { my $count; sub increment { return $count++ } } 

闭包是定义$countvariables的环境。 它只能用于increment子程序,并在两次调用之间保持不变。