函数指针,闭包和Lambda

23 浏览
0 Comments

函数指针,闭包和Lambda

我刚开始学习函数指针,并且在阅读K&R关于这个主题的章节时,首先让我想到的是,“嘿,这有点像闭包”。我知道这个假设在根本上是错误的,经过在线搜索后,我并没有找到关于这种比较的真正分析。

那么为什么C风格的函数指针与闭包或lambda表达式在根本上是不同的呢?据我所知,这与函数指针仍然指向一个已定义(命名)的函数有关,而不是匿名定义函数的做法。

为什么在第二种情况下,将一个函数传递给另一个函数被认为比第一种情况更强大,而在第一种情况下,只是传递一个普通的、日常使用的函数?

请告诉我我为什么错误地将这两者比较得如此紧密。

0
0 Comments

在C语言中,无法内联定义函数,因此无法创建闭包。你只是在传递一些预定义方法的引用。而支持匿名方法/闭包的语言中,方法的定义更加灵活。

简单来说,函数指针没有与之关联的作用域(除非算上全局作用域),而闭包则包括定义它们的方法的作用域。通过使用lambda表达式,你可以编写一个生成方法的方法。闭包允许你将“某些参数绑定到一个函数,并得到一个较低数量参数的函数作为结果。”(引自Thomas的评论)。这在C语言中无法实现。

编辑:添加一个示例(我将使用类似ActionScript的语法,因为我现在脑子里想的就是这个):

假设你有一个以另一个方法作为参数的方法,但在调用该方法时没有提供任何参数的方式?比如,假设你有一个在运行传递给它的方法之前产生延迟的方法(愚蠢的例子,但我想保持简单)。

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

现在假设你想使用runLater()延迟处理某个对象:

function objectProcessor(o:Object):Void {

/* 对对象进行一些酷炫的处理! */

}

function process(o:Object):Void {

runLater(function() { objectProcessor(o); });

}

你传递给process()的函数不再是一些静态定义的函数。它是动态生成的,并且能够包含在定义该方法时作用域中的变量的引用。因此,它可以访问'o'和'objectProcessor',即使它们不在全局作用域中。

希望这样讲清楚了。

根据你的评论,我根据你的评论修改了我的答案。我对术语的具体细节仍然不是100%清楚,所以我直接引用了你的话。:)

匿名函数的内联能力是(大多数?)主流编程语言的一种实现细节,而不是闭包的要求。

0
0 Comments

函数指针、闭包和Lambda的问题出现的原因是希望在C语言中实现类似于函数闭包的功能。函数闭包是指一个包含函数和环境的集合体,其中环境是指函数中的自由变量的集合。在一些函数式编程语言中,闭包不会动态地创建一个新的函数,而是重用现有的函数,并在其上下文中使用新的自由变量。

在Standard ML of New Jersey编译器中,闭包被表示为一个记录,其中一个字段包含指向代码的指针,其他字段包含自由变量的值。编译器通过分配一个新的记录来动态地创建一个新的闭包,其中包含指向相同代码的指针,但具有不同的自由变量的值。

在C语言中可以模拟这个过程,但是非常麻烦。有两种常用的技术:

1. 将函数(代码)的指针和自由变量的指针分开传递,以便闭包跨两个C变量分布。

2. 传递一个包含自由变量值和代码指针的结构体的指针。

第一种技术适用于在C语言中模拟某种多态性的情况,而不想暴露环境的类型,可以使用void*指针来表示环境。你可以参考Dave Hanson的《C Interfaces and Implementations》中的例子。第二种技术更接近函数式语言的本机代码编译器的实现,也类似于另一种常见的技术...具有虚拟成员函数的C++对象。这两种实现几乎相同。

这种观察引起了Henry Baker的一个俏皮话:

“Algol/Fortran领域的人们多年来一直抱怨不理解函数闭包在未来的高效编程中可能有什么用处。然后发生了'面向对象编程'的革命,现在每个人都使用函数闭包进行编程,只是他们仍然拒绝称之为函数闭包。”

这个观点解释了面向对象编程实际上就是使用闭包,即“重用现有的函数但使用新的自由变量”的概念。函数闭包接受环境(指向对象实例数据的结构体指针,它只是新状态)来操作。

0
0 Comments

函数指针、闭包和Lambda的问题和解决方法

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

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

我在这里使用了匿名委托作为闭包(它的语法比lambda等价物更清晰、更接近C),它将lessThan(一个栈变量)捕获到闭包中。当闭包被评估时,lessThan(其栈帧可能已被销毁)将继续被引用。如果我改变lessThan,那么我改变了比较:

int lessThan = 100;
Func lessThanTest = delegate(int i) {
   return i < lessThan;
};
lessThanTest(99); // 返回true
lessThan = 10;
lessThanTest(99); // 返回false

在C中,这是不合法的:

BOOL (*lessThanTest)(int);
int lessThan = 100;
lessThanTest = &LessThan;
BOOL LessThan(int i) {
   return i < lessThan; // 编译错误 - lessThan不在作用域内
}

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

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

但是,现在我在评估它时必须传递这两个参数。如果我希望将这个函数指针传递给另一个作用域中没有lessThan的函数,我要么必须通过将其传递给链中的每个函数来手动保持其有效,要么将其提升为全局变量。

虽然大多数支持闭包的主流语言使用匿名函数,但并没有这样的要求。你可以有没有匿名函数的闭包,也可以有没有闭包的匿名函数。

闭包是函数指针和捕获变量的组合。

在你编写这段代码时可能使用了较旧版本的C,或者没有记得前向声明函数,但是当我测试这段代码时,并没有观察到你提到的相同行为。你将lessThan变量作为全局变量,我明确提到了这一点作为另一种选择。

你的第一个C片段确实将`int lessThan`作为全局变量,对吧?那么为什么会被认为是非法的呢?

0