匿名函数
在计算机编程中,匿名函数(英语:anonymous function)是指一类无需定义标识符(函数名)的函数或子程序,普遍存在于多种编程语言中。
1958年LISP首先采用匿名函数,自此之后,越来越多编程语言陆续采用,主流的编程语言如PHP[1]和C++[2]也在不久前采用。
目录
1 用途
1.1 排序
2 语言列表
3 示例
3.1 Python
3.2 JavaScript
3.3 PHP
3.3.1 4.0.1 至 5.3
3.3.2 5.3
3.4 C++
3.4.1 C++ 98/03
3.4.2 C++ 11
3.4.3 C++14
3.5 Visual Basic.NET
4 参考资料
5 外部链接
用途
排序
尝试将类按名称排序:
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(...)
该匿名函数接受两个变量 x
和 y
,通过内部函数 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开始支援,标准中称之为lambda表达式(lambda expression)[3][4] | |
Clojure | ||
Curl | ||
D | ||
Delphi | 从Delphi 2009开始支援 | |
Dylan | ||
Erlang | ||
F# | ||
Frink | ||
Go | ||
Haskell | ||
Java | 从Java 8开始支援[5] | |
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)。 | |
Rust | Rust的匿名函式可以使用“閉包”(Closures)來實現。 | |
Scala | ||
Scheme | ||
Smalltalk | Smalltalk的匿名函数称为“块”(block) | |
Visual Basic .NET v9 | ||
Visual Prolog v 7.2 | ||
Vala |
示例
Python
Python用lambda语法定义匿名函数,只需用表达式而无需声明
# 以下两种相等同
# 1.不使用匿名函数
def f(x):
return x * x
# 2.使用匿名函数
lambda x: x * x
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之前不支持匿名函数[6]。
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()
[7]。Lambda函数都是编译器的一种“花招”[8],它能产生新的能被调用的Closure
实例,就像函数能被调用一样。
$x = 3;
$func = function($z) { return $z *= 2; };
echo $func($x); // 输出结果为6
上述例子中的$func
是Closure
类的一个实例,而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++ 98/03
C++ 98/03标准并不原生支持匿名函数。不过可以利用Boost库的Boost.Lambda来实现一个匿名函数[9]。
C++ 11
C++11标准提供了匿名函數的支持,在《ISO/IEC 14882:2011》(C++11标准文档)中叫做lambda表達式[10]。一個lambda表達式有如下的形式:
[capture] (parameters) mutable exception attribute -> return_type { body }
必须用方括号括起来的capture列表来开始一个lambda表达式的定义。
lambda函数的形参表比普通函数的形参表多了3条限制:
- 参数不能有缺省值
- 不能有可变长参数列表
- 不能有无名参数
如果lambda函数沒有形參且没有mutable、exception或attribute声明,那麼参数的空圆括號可以省略。但如果需要给出mutable、exception或attribute声明,那么参数即使为空,圆括号也不能省略。
如果函數體只有一個return語句,或者返回值類型為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表达式生成的为一个函数对象,它的operator()
成员缺省有const属性)。修改这些傳值捕獲变量将导致编译报错。但在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表达式声明在类成员函数中,对象的this指针必需显式捕获声明。
- 具有静态存储期的变量(如全局变量)
- 被捕获的外部变量
- 显式捕获的变量
- 隐式捕获的变量,使用默认捕获模式(传值或引用)来访问。
lambda函数的数据类型是函数对象,保存时必须用std::function
模板类型或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.
C++14
C++14增加了广义捕获(Generalized capture)。[11]即在捕获子句(capture clause)中增加并初始化新的变量,该变量不需要在lambda表达式所处的闭包域(enclosing scope)中存在;即使在闭包域中存在也会被新变量覆盖(override)。新变量类型由它的初始化表达式推导。一个用途是可以从闭包域中捕获只供移动的变量并使用它。
C++14还允许lambda函数的形参使用auto
关键字作为其类型,这实质上是函数对象的operator()
成员作为模板函数;并且允许可变参数模板。
auto a_lambda_func = [data1=101](int x) { /*...*/ }; //广义捕获,实质上是在函数对象中增加了数据成员data1并初始化
auto ptr = std::make_unique<int>(10); //See below for std::make_unique
auto lambda1 = [ptr = std::move(ptr)] {return *ptr;}
//大致等效于:
auto lambda2 = [ptr = std::make_unique<int>(10)] {return *ptr;}
auto lambda3 = (auto x, auto y) {return x + y;} //lambda函数的形参类型为auto
struct unnamed_lambda //这相当于函数对象:
{
template<typename T, typename U>
auto operator()(T x, U y) const {return x + y;}
};
auto lambda4 = (auto&&... params) //可变参数的函数模板
{
return (foo(std::forward<decltype(params)>(params)...));
}
Visual Basic.NET
匿名函数或lambda表达式即无名的函数或过程,作为表达式的值。可以写为一行或多行。例如:
Dim func1=Function(i As integer) i+10
Dim action = sub()
End Sub
Dim func2 = Function()
End Function
可以在声明匿名函数的同时调用它。单行的lambda表达式不能使用Return关键字,其返回类型是自动推导得出;其参数要么都是用As关键字指明类型,要么全部是自动推导出类型。
lambda表达式在定义时可以使用所在上下文(context,即C++语言的闭包closure)的局部变量、参数、属性、Me等等的值,即使lambda表达式离开了定义时所在的context,这些被使用的局部变量等的值仍然有效。这是因为lambda表达式在定义时把所用到的context的值保存到它自己的定义类中。lambda表达式可以嵌套定义。
参考资料
^ Anonymous functions (英语).Anonymous functions, also known as closures, allow the creation of functions which have no specified name. They are most useful as the value of callback parameters, but they have many other uses.
^ 5.1.2 Lambda expressions. ISO/IEC 14882:2011 (英语).Lambda expressions provide a concise way to create simple function objects.
^ Lambda expressions and closures for C++ (PDF). V Samko; J Willcock, J Järvi, D Gregor, A Lumsdaine. 2006-02-26 [2010-06-01].
^ 5.1.2 Lambda expressions. ISO/IEC 14882:2011 (英语).Lambda expressions provide a concise way to create simple function objects.
^ Java 7 Features. Sun Microsystems. 2010-02-09 [2010-11-21].
^ http://php.net/create_function the top of the page indicates this with "(PHP 4 >= 4.0.1, PHP 5)"
^ http://wiki.php.net/rfc/closures
^ http://wiki.php.net/rfc/closures#zend_internal_perspective
^ Chapter 16. Boost.Lambda - 1.55.0 (英语).
^ 该标准文档可以在ISO/IEC 14882:2011 - Information technology -- Programming languages -- C++购买,或者可参看2012年一月份的标准草案文件N3337,第5.1.2节的Lambda expressions
^ MSDN: In C++14, you can introduce and initialize new variables in the capture clause, without the need to have those variables exist in the lambda function’s enclosing scope. The initialization can be expressed as any arbitrary expression; the type of the new variable is deduced from the type produced by the expression. One benefit of this feature is that in C++14 you can capture move-only variables (such as std::unique_ptr) from the surrounding scope and use them in a lambda.
外部链接
《深入理解PHP内核》第四章 函数的实现 第四节 匿名函数及闭包[永久失效連結]