The ABC of Dart Part 2 - 函数

函数的写法跟其他语言基本大同小异,如下:

1
2
3
int func1(String str){
return int.parse(str);
}

在以上的函数func1中,声明了返回值为int类型的对象,函数参数为String,这是一种标准写法。

函数的简写

动态脚本语言,或者函数式编程语言,特点之一就是函数可以简写,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
int func1(String str){
return 1;
}

func2(String str){
return 1;
}

func3(str){
return 1;
}

func4(str) => 1;

因为在Dart中类型是可选的,所以像函数func2缺省声明返回值类型、函数func3缺省声明参数类型的写法都可以,在这种情况下,编译器默认将缺省声明类型的都声明为dynamic。不过个人建议还是不要这样省略,dynamic会让程序员感到困惑。

函数func4Dart提供的一种新的写法,类似于Java的lambda,当函数内只有一行语句时,可以用=>来简写,即func4代表的函数类型和func3func2一样。

函数对象 - Function

前面说过,Dart中一切皆为对象,函数也不例外,它的对象类型为Function

1
2
3
4
5
void func1(String str){
//TODO...
}

print(func1 is Function);// 打印true

像这样,通过Dartis关键字用来判断类型,可知函数func1的类型就是Function

但就我的理解,Function对于函数而言,就是一个基类。所有函数都的类型都基于Function,而每一种函数都有其特定的类型。

比如,上述的函数func1,可以用对象的概念套进去,即func1的基本类型是Function,实际类型是(String) -> void,而fucn1则是这个实际类型的实例的名称。

我认为,需要先理解这个概念,才能明白为何把函数称为第一公民,为何认为一切皆对象。不仅仅是Dart这么语言,目前大多的动态语言、函数式语言都是类似的这种设定。

闭包

函数作为一种对象,既然有匿名对象,也就有匿名函数,如下:

1
2
3
4
5
6
7
8
//(1)
(String name) => "Hello $name";

//(2)
(name){
print("Hello $name");
return 1;
}

像(1)、(2)这种把函数名、返回类型缺省的写法就是匿名函数。得益于函数也是对象这个概念,匿名函数这种写法才能实现。

以(1)为例子,匿名函数的写法舍去了函数名和返回类型,编译器会自动推断这段函数的返回类型,同时这段函数的实际类型为(String) -> String

如果这样一看,就像把一段封装好的逻辑包裹在函数这个对象的体内,因为函数自为对象,所以这个函数具备传递性,它可以被任意拿去调用,而不再像面向对象的语言一样,函数方法依附于实体类,这里就引申出函数式编程最重要的概念:闭包。

函数作为一种对象,因此可以作为返回值,也可以作为参数被传递。关于闭包的概念,寥寥几句无法说得清除。这里给出一种比较思路:比如在Java中,函数方法是作为一个对象的成员,我们要调用一个函数方法,调用它的这段逻辑,必须先传递对象,获取对象;反过来在Dart中,我们也是需要传递对象、获取对象,只是函数此时变成了一个对象,实际上我们依然在跟对象打交道,只是函数对象的出现使得一段逻辑本身就能被打包成一个可传递可获取的对象。

但是闭包不仅限于表示一段函数,而是一段包含了上下文环境的函数,这里还是不多说,只用一段代码表示一下

1
2
3
4
var func = (int a){
int b = 4;
return (int c) => a - b - c;
}(1);

在这段代码中,这段代码先定义了一段匿名函数,我们把该匿名函数叫做f1,f1的类型为(int) -> Function,表示返回值是函数。然后我在匿名函数f1后面写上(1),表示调用f1,传入参数1。f1的内部声明了一个变量int b = 4,并返回了一段匿名函数,我们把这段匿名函数叫做f2,f2的类型为(int) -> int。因为我们调用了f1,所以立刻返回了f2并赋值给声明为动态推断类型的变量func,所以func的类型就是Function,它的实际类型就是f2这段函数。

注意这里的f2函数,其实携带着它的上下文环境——即int b = 4这个变量的。关于这个上下文环境不再多说,此处只作为一个例子。而从这个例子也可以得知,闭包的内存消耗比一般的对象可能要大,因为它包含了上下文环境

回到开头所说的匿名函数,因为它缺省了函数名,所以在声明一段匿名函数时,必须立刻给它赋值,或者立刻执行它。所以开头的代码(1)、(2)应该这样写:

1
2
3
4
5
6
7
8
//(1)
var func1 = (String name) => "Hello $name";

//(2)
(name){
print("Hello $name");
return 1;
}("world");

注意:

  1. 将匿名函数赋值给某个变量时,需要将变量声明为varFunction
  2. 匿名函数无法在函数外或类外立刻调用,只能声明。

typedef

typedefDart中的关键字。我的理解是用于定义函数类型,即将一种函数类型定义成一种对象;而网上的说法是typedef用于定义函数类型的别名。

1
typedef int Compare(Object a, Object b);

在上述代码中,将(Object, Object) -> int这种函数的实际类型给予了一个具体的名称Compare,这意味着,一旦任何一个函数的实际类型是(Object, Object) -> int,则这个函数的实际类型等同于Compare。例如:

1
2
3
4
5
int compareFunc(Object a1, Object b1){
return 0;
}

print(compareFunc is Compare);//打印true

上述代码中的函数compareFunc的实际类型为(Object, Object) -> int,所以用is来与Compare作判断的结果为true。

之前说过Function代表函数类型,所以作为一种类型,也是可以定义为函数的返回值类型和参数类型。如:

1
2
3
Function getFunc(int a){
return (int b) => b - a;
}

因为用typedef定义了一段函数类型的名字,所以可以用该名字作为返回值类型或参数类型,如:

1
2
3
4
5
Compare getFunc(int i){
return (Object a,Object b){
return i;
};
}

可选参数

可选参数是Dart在函数上增加的新特性,作用如名字一样,当在函数中声明了可选参数,在调用该函数时我们可以选择是否传递该可选参数。

可选参数有两种:

命名可选参数

命名可选参数用大括号{}包围,默认值可用:=来赋值。

1
2
3
4
5
6
7
8
void func(int a, {int b:3, c, d}){
//TODO
}

//调用
func(1); //(1)
func(1,b:4); //(2)
func(1,c:5); //(2)

上述代码中,我们定义了func函数,函数参数中必须要传的是a,剩下的是可传可不传的参数bcd,我们用大括号包围。其中某些可选参数我们想要有一个默认值,可以在声明时就用:或者=来赋值,如参数b

在调用时,我们可以只传必须要传的参数,如(1)。如果我们想要传入某些参数,比如我们要传入参数b,则可以如(2)一样调用,注意因为是命名可选参数,所以在传入可选参数时必须声明传给哪个参数。

位置可选参数

位置可选参数用方括号[]包围,默认值用=来赋值。

1
2
3
4
5
6
7
void func(int a , [int b = 3, c, d]){
//TODO
}

//调用
func(1); //(1)
func(1,2,3); //(2)

上述代码中,我们定义了func函数,函数参数中必须要传的是a,剩下的是可传可不传的参数bcd,我们用中括号包围。其中某些可选参数我们想要有一个默认值,可以在声明时就用=来赋值,如参数b

表面上,位置可选参数和命名可选参数区别不大。实际上它们的区别在调用时的传参:命名可选参数传参时必须声明传给哪个参数,而位置可选参数则是按顺序来传值的。在(1)中只传必须参数时和命名可选参数一样。但在(2)中可以看到,传参时不需要声明参数名,当我们传入的参数为123时,函数将分别把参数赋值到abc中。

The ABC of Dart Part 3 - 类 The ABC of Dart Part 1 - 基础

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×