匿名函数

维基百科,自由的百科全书
跳转至: 导航搜索

在计算机编程中,匿名函数英语anonymous function)是指一类无需定义标识符(函数名)的函数子程序,普遍存在于多种编程语言中。

1958年LISP首先采用匿名函数,自此之后,越来越多编程语言陆续采用,主流的编程语言如PHP[1]也在不久前采用。其中在JavaScript中的匿名函数最为人熟悉。[來源請求]

用途[编辑]

排序[编辑]

尝试将类按名称排序:

a = [10, '10', 10.0]
a.sort(lambda x,y: cmp(x.__class__.__name__, y.__class__.__name__))
print a
[10.0, 10, '10']

上述 10.0 的类名是“float”,10 的类名是“int”而 '10' 的类名是“str”,排列后的顺序为“float”“int”,接着是“str”。

该示例中的匿名函数就是lambda表达式:

lambda x,y: cmp(...)

该匿名函数接受两个变量 xy ,通过内部函数 cmp() 返回两者的比较值,下面的例子将按长度为字符串列表排序:

a = ['three', 'two', 'four']
a.sort(lambda x,y: cmp(len(x), len(y)))
print a
['two', 'four', 'three']

语言列表[编辑]

语言 支持 备注
ActionScript 是
C 否 在有clang和llvm的compiler-rt程序库的环境下支援
C# 是 從C# 3.0 (.Net Farmework 3.5)開始支援
C++ 是 C++11开始支援[2]
Clojure 是
Curl 是
D 是
Delphi 是 从Delphi 2009开始支援
Dylan 是
Erlang 是
F# 是
Frink 是
Go 是
Haskell 是
Java 是 Java 8开始支援[3]
JavaScript 是
Lisp 是
Logtalk 是
Lua 是
Mathematica 是
Matlab 是
ML语言
(Objective Caml, Standard ML, etc.)
是
Octave 是
Object Pascal 是 原生支援匿名函数,其正式名称为“匿名方法”(anonymous method)。Oxygene Object Pascal也支持匿名函数。
Objective-C (Mac OS X 10.6+) 是 称作“块”(block)
Pascal 否
Perl 是
PHP 是 从PHP 5.3.0开始支援真匿名函数,之前则只支持部分匿名函数
Python 是 Python用lambda语法定义匿名函数,只需用表达式而无需声明
R 是
Ruby 是 Ruby的匿名函数源自Smalltalk,也同样叫“块”(block)。
Scala 是
Scheme 是
Smalltalk 是 Smalltalk的匿名函数称为“块”(block)
Visual Basic .NET v9 是
Visual Prolog v 7.2 是
Vala 是

示例[编辑]

JavaScript[编辑]

JavaScript支持匿名函数。

alert((function(x){
	return x*x;
})(10)); // 提示100

小书签也经常使用这种结构,例如下面的一个小书签就将当前网页的标题显示为其URL:

javascript:document.title=location.href;

然而,由于该赋值语句返回了一个值(即URL本身),很多浏览器会同时创建一个新的页面显示这个值。

取而代之,下面的匿名函数就可以做到不返回任何值:

javascript:(function(){document.title=location.href;})();

第一对圆括号中的函数(“(function(){document.title=location.href;})”)用作声明一个匿名函数,而最后的一对圆括号则用来执行这个函数。同等用法有:

javascript:var f = function(){document.title=location.href;}; f();

PHP[编辑]

PHP 4.0.1之前不支持匿名函数[4]

4.0.1 至 5.3[编辑]

PHP 4.0.1新增加了create_function函数,这是匿名函数的雏形。该函数能创建一个随机命名的新函数并以字符串形式返回新函数的函数名。

$foo = create_function('$x', 'return $x*$x;');
$bar = create_function("\$x", "return \$x*\$x;");
echo $foo(10);

要注意的是,新函数本身及其变量都要放在单引号里面,如果要放在双引号之内,美元符号“$”则需要转码成为“\$”。

5.3[编辑]

PHP 5.3新增加了Closure类,以及能使类的实例可被调用的“魔术方法”__invoke()[5]。Lambda函数都是编译器的一种“花招”[6],它能产生新的能被调用的Closure实例,就像函数能被调用一样。

$x = 3;
$func = function($z) { return $z *= 2; };
echo $func($x); // 输出结果为6

上述例子中的$funcClosure类的一个实例,而echo $func()则相当于是$func->__invoke($z)。PHP 5.3模仿使用匿名函数,但并非支持真匿名函数,因为PHP的函数仍非第一类函数。

虽然PHP 5.3支持闭包,但还需要像这样明确标识其变量:

$x = 3;
$func = function() use(&$x) { $x *= 2; };
$func();
echo $x; // 输出结果为6

$func引用了变量$x(&$x),在调用的时候就会修改原来的$x,其结果在函数以外的地方也是可见的。

C++[编辑]

C++11提供了匿名函數的支持,在標準中叫做lambda表達式。一個lambda表達式有如下的形式:

[capture](parameters)->return-type{body}

必须用方括号括起来的capture列表来开始一个lambda表达式的定义。

lambda函数的形参表比普通函数的形参表多了3条限制:

  1. 参数不能有缺省值
  2. 不能有可变长参数列表
  3. 不能有无名参数

如果lambda函数沒有形參且没有mutable与exception声明,那麼参数的空圆括號可以省略。但如果需要给出mutable或exception声明,那么参数即使为空,圆括号也不能省略。

如果函數體只有一個turn語句,或者返回值類型為void,那麼返回值類型声明可以被省略:

[capture](parameters){body}

一個lambda函數的例子如下:

[](int x, int y) { return x + y; } // 從return語句中隱式獲得的返回值類型
[](int& x) { ++x; }   // 沒有return語句 -> lambda函數的返回值為void
[]() { ++global_x; }  // 沒有參數,僅僅是訪問一個全局變量
[]{ ++global_x; }     // 與前者相同,()可以被省略

這個無名函數的返回值是decltype(x+y) (在上面的第一個例子中)。如果lambda函數體的形式是return expression,或者甚麼也每返回,或者所有返回語句用decltype都能檢測到同一類型,那麼返回值類型可以被省略。

返回值類型可以显式指定,如下所示:

[](int x, int y) -> int { int z = x + y; return z; }

在這個例子中,一個臨時變量,z,被創建來儲存中間過程。與一般的函數一樣,中間值在調用的前後並不存在。甚麼也沒有返回的lambda表達式無需顯式指定返回值,沒有必要寫-> void代碼。

lambda函數可以捕获lambda函數外的具有automatic storage duration的变量。函数体与這些變量的集合合起来称做閉包。这些外部变量在聲明lambda表達式時列在在方括號[]中。空的方括号表示没有外界变量被capture。這些變量被傳值捕獲或者引用捕獲。对于传值捕获的变量,默认为只读。修改这些变量将导致编译报错。但在lambda表达式的参数表的圆括号后面使用mutable关键字,就允许lambda函数体内的语句修改传值引用的变量,这些修改与lambda表达式(实际上是用函数对象实现)有相同的生命期,但不影响被传值捕获的外部变量的值。lambda函数可以直接使用具有static存储期的变量。如果在lambda函数的捕获列表中给出了static存储期的变量,编译时会给出警告,仍然按照lambda函数直接使用这些外部变量来处理。因此具有static存储期的变量即使被声明为传值捕获,修改该变量实际上直接修改了这些外部变量。编译器生成lambda函數对应的函数对象时,不会用函数对象的数据成员来保持被“捕获”的static存储期的变量。示例:

[]        // 沒有定義任何變量,但必须列出空的方括号。在Lambda表達式中嘗試使用任何外部變量都會導致編譯錯誤。
[x, &y]   // x是按值傳遞,y是按引用傳遞
[&]       // 任何被使用到的外部變量都按引用傳入。
[=]       // 任何被使用到的外部變量都按值傳入。
[&, x]    // x按值傳入。其它變量按引用傳入。
[=, &z]   // z按引用傳入。其它變量按值傳入。

下面這個例子展示了lambda表達式的使用:

std::vector<int> some_list{ 1, 2, 3, 4, 5 };
int total = 0;
std::for_each(begin(some_list), end(some_list), 
                 [&total](int x) {  total += x; }
              );

在类的非静态成员函数中定义的lambda表达式可以显式或隐式捕捉this指针,从而可以引用所在类对象的数据成员与函数成员。

lambda函数的函数体中,可以访问下述变量:

  • 函数参数
  • 局部声明的变量
  • 类数据成员(当函数声明在类中)
  • 具有静态存储期的变量(如全局变量)
  • 被捕获的外部变量
    • 显式捕获的变量
    • 隐式捕获的变量,使用默认捕获模式(传值或引用)来访问。

lambda函数的数据类型是函数对象,保存时必须用std::function模板类型或auto关键字,或用auto关键字。例如:

#include <functional>
#include <vector>
#include <iostream>
 
double eval(std::function <double(double)> f, double x = 2.0)
{
	return f(x);
}
 
int main()
{
	std::function<double(double)> f0    = [](double x){return 1;};
	auto                          f1    = [](double x){return x;};
	decltype(f0)                  fa[3] = {f0,f1,[](double x){return x*x;}};
	std::vector<decltype(f0)>     fv    = {f0,f1};
	fv.push_back                  ([](double x){return x*x;});
	for(int i=0;i<fv.size();i++)
		std::cout << fv[i](2.0) << std::endl;
	for(int i=0;i<3;i++)
		std::cout << fa[i](2.0) << std::endl;
	for(auto &f : fv)
		std::cout << f(2.0) << std::endl;
	for(auto &f : fa)
		std::cout << f(2.0) << std::endl;
	std::cout << eval(f0) << std::endl;
	std::cout << eval(f1) << std::endl;
	std::cout << eval([](double x){return x*x;}) << std::endl;
	return 0;
}

一个lambda函数的捕捉表达式为空,则可以用普通函数指针存储或调用。例如:

auto a_lambda_func = [](int x) { /*...*/ };
void (* func_ptr)(int) = a_lambda_func;
func_ptr(4); //calls the lambda.

参考资料[编辑]

  1. ^ Anonymous functions. [2011-02-17]. "Anonymous functions, also known as closures, allow the creation of functions which have no specified name." 
  2. ^ Lambda expressions and closures for C++. V Samko; J Willcock, J Järvi, D Gregor, A Lumsdaine. 2006-02-26 [2010-06-01]. 
  3. ^ Java 7 Features. Sun Microsystems. 2010-02-09 [2010-11-21]. 
  4. ^ http://php.net/create_function the top of the page indicates this with "(PHP 4 >= 4.0.1, PHP 5)"
  5. ^ http://wiki.php.net/rfc/closures
  6. ^ http://wiki.php.net/rfc/closures#zend_internal_perspective

外部链接[编辑]