函数类型、匿名函数与闭包
在Dart里,函数像普通变量一样,可作为参数传递,或从一个函数返回,这便是所谓的函数乃一等公民(first-class object)。在Dart里可以定义匿名函数 ,也就是没有名称的函数,这在实际应用中极为方便。闭包(Closure)是与之相关的另一个重要的概念,详见后文。
函数类型
首先通过一个示例,来看一看什么是函数类型(funtion type)。
// ch0206_1.dart
void main() {
int increase(int value, {int increment = 1}) => value + increment; // 3
var increaseFunc = increase; // 4
print(addFunc); // 5
print(increaseFunc); // 5a
//Output:
//Closure: (int, int) => int from Function 'add': static.
//Closure: (int, {int increment}) => int
}
int add(int a, int b) => a + b; // 1
int Function(int a, int b) addFunc = add; // 2
int Function(int, int) addFunc2 = add; // 2a
var addFunc3 = add; // 2b
- 声明函数
add
; - 声明变量
addFunc
并赋值为add
,它的类型是一个函数类型int Function(int a, int b)
, 可见函数类型的写法与声明一个函数类似,去掉函数体,然后将函数名称换成关键字Function
即可,同时可省略位置参数的名称(2a); - 在
main
函数的内部,声明函数increase
; - 声明变量
increaseFunc
并赋值为increase
; - 打印
addFunc
与increaseFunc
,注意观察其输出。
increase
函数定义在main
函数的内部,这便是嵌套函数的概念。Closure: (int, int) => int from Function 'add': static.
,这表明addFunc
的值是一个闭包(Closure),并且它来自全局(static
)的add
函数。这里的全局讲的是作用域(Lexical Scope)。
使用 typedef
使用 typedef
关键字给函数类型取一个别名:
typedef AddFunction = int Function(int a, int b);
AddFunction addFunc4 = add;
typedef VoidCallback = void Function();
typedef
也用于给数据类型取别名:
typedef IntList = List<int>;
词法范围(Lexical Scope)
从语法上看,一对花括号({}
)便定义了一个词法范围,即作用域。作用域嵌套而成,外层作用域内的变量对内层可见,反之不然。下面是来自官方文档的示例:
// ch0206_2.dart
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}
匿名函数
像main
、print
这些是命名(具名)函数。我们也可以创建没有名称的函数,称之为匿名函数,或lambda表达式 或 闭包。
匿名函数经常作为其他函数的参数,它与命名函数类似,特别之处在于:
- 没有函数名称;
- 也无需声明返回值的类型;
- 参数的类型是可选的。
语法格式:
(参数列表) {
函数体
}
下面的示例程序分析一句英文,然后打印其中的单词及其长度。
// ch0206_3.dart
void main() {
var sentence = 'Its quaint events were hammered out'; // 1
var stats = sentence.split(' ').map((word) => (word, word.length)); // 2
stats.forEach(print); // 3
// Output:
// (Its, 3)
// (quaint, 6)
// (events, 6)
// (were, 4)
// (hammered, 8)
// (out, 3)
}
sentence
是一句英文;sentence.split(' ')
使用空格符(' '
)分割sentence
,得到一个单词(word
)的列表; 然后将每个单词映射(map
)为一个记录,内容为单词及其长度((word, word.length)
); 这一句结束时将得到统计结果stats
,它是一个Iterable
对象(可将Iterable
简单理解为一个更为泛化的列表,通过特定的方法访问其元素);- 使用
Iterable.forEach
方法,打印stats
中的每个元素,这里将print
函数作为参数传入forEach
。
第2句中的(word) => (word, word.length)
是一个匿名函数,它作为参数传入map
函数。
闭包(Closure)
闭包是一个容易让人迷糊的概念。简言之,闭包是一个函数(或函数值、函数对象),很多时候它是一个匿名函数。闭包这个概念之所以会诞生,是因为它的实用性与特殊性:
闭包即使脱离了定义它的作用域(原始作用域),依然可以访问原始作用域内的变量。
如果一门编程语言具有上述特性,比如Dart,我们就说Dart支持闭包,可见闭包这个词也用来表示编程语言的这一特点(feature)。
下面这个示例来自官方文档:
// ch0206_4.dart
Function makeAdder(int addBy) {// 1
return (int i) => addBy + i; // funcA
}
void main() {
var add2 = makeAdder(2); // 2
var add4 = makeAdder(4); // 2a
assert(add2(3) == 5); // 3
assert(add4(3) == 7); // 3a
}
makeAdder
是一个函数,它返回一个匿名函数(int i) => addBy + i;
,为方便描述,称之为funcA
;- 分别调用
makeAdder(2)
、makeAdder(4)
得到函数add2
、add4
; - 断言
add2(3)
等于5, 断言add4(3)
等于7。
add2
、add4
都记住了makeAdder
被调用时的addBy
(makeAdder
的参数)。从数学的角度来看funcA
:
\( funcA( i ) = addBy + i \)
这个函数里的自变量是i
, addBy
是一个常量。 add2 = makeAdder(2)
, 相当于指定常量 addBy = 2
,add4
类似。add2
、add4
等都是闭包。
练习
修改示例程序ch0206_3.dart
,将统计结果按单词的长度降序排序。提示:使用List.sort
方法。