函数类型、匿名函数与闭包
在Dart里,函数像普通变量一样,可作为参数传递,或从一个函数返回,这便是所谓的函数乃一等公民(first-class object)。在Dart里可以定义匿名函数 ,也就是没有名称的函数,这在实际应用中极为方便。闭包(Closure)是与之相关的另一个重要的概念,详见后文。
函数类型
首先通过一个示例,来看一看什么是函数类型(funtion type)。
// ex261.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)
从语法上看,一对花括号({})便定义了一个词法范围,即作用域。作用域嵌套而成,外层作用域内的变量对内层可见,反之不然。下面是来自官方文档的示例:
// ex262.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表达式 或 闭包。
匿名函数经常作为其他函数的参数,它与命名函数类似,特别之处在于:
- 没有函数名称;
- 也无需声明返回值的类型;
- 参数的类型是可选的。
语法格式:
(参数列表) {
函数体
}
下面的示例程序分析一句英文,然后打印其中的单词及其长度。
// ex263.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)。
下面这个示例来自官方文档:
// ex264.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 等都是闭包。
练习
修改示例程序ex263,将统计结果按单词的长度降序排序。提示:使用List.sort方法。