3.1 使用函数
函数是编程中最伟大的发明。有了函数,我们可以将复杂的问题简单化,将庞大的程序分解开。函数使得代码复用真正实现,并且在其基础上产生了更加面向应用的编程方式。本节我们就来学习Dart中函数的应用。
3.1.1 关于main函数
任何Dart应用程序都需要有一个main函数作为整个应用程序的入口。我们之前编写的所有程序都有一个main函数,main函数不一定要在程序的开头,但是它一定会作为程序的入口,在main函数中,我们可以编写逻辑代码,也可以调用其他函数。
在数学中,函数代表了一种运算规则,函数有定义域、值域和映射关系3要素。在编程中,函数的概念与数学中函数的概念相差不多,也有3要素,分别为参数、返回值和函数体。任何一个函数都由这3部分组成,在平时编写main函数时,我们隐藏了返回值并且忽略了参数,一个完整的main函数编写如下:
可以发现,main函数其实是一个返回值是void并且有一个字符串列表参数的函数。我们之前运行Dart代码时,从没有向main函数中传递过参数,其实可以传递任意个字符串参数进去,可以在终端使用Dart工具来执行上面的main函数,并且进行参数的传递,代码如下:
dart func1.dart 珲少 hello
执行指令后,终端会输出接收到的参数列表:[珲少, hello]。
3.1.2 自定义函数
Dart系统库为我们编写了许多常用的函数。例如,在学习数据类型的时候,每一种数据类型中都封装有对应的函数,通过这些函数我们可以方便地实现一些通用的功能。但是Dart并不是万能的,它只能提供基础的工具,要打造出精美的应用,除了要使用这些基础的工具外,必要的时候还需要借助别人创造的工具或者使用自己创造的工具。
函数就是Dart中基础的工具,自定义函数也很简单,只要把握住函数的3要素即可,格式如下:
可以编写一个简单的加法器函数,代码如下:
这是一个有两个num类型的参数并且返回值为num类型的函数,a和b为传递进函数的参数名,在函数体内可以通过参数名来获取参数的值进行使用,通过函数体的运算后,使用return语句将结果返回。在main函数中对自定义的函数体进行调用,代码如下:
通过函数名加小括号的方式可以对函数进行调用,小括号中可以传递函数所需的所有参数。需要注意,在调用函数时,传递进函数的参数个数与类型必须和函数定义的参数个数和类型完全一致,数量不符或类型不匹配都会产生错误。
其实Dart是一门真正的面向对象语言,也就是说,不论是字符串、数值、布尔值还是函数,在Dart中都一视同仁,它们都是对象。因此,它们都有对象的行为和特性,你可以将函数对象赋值给一个变量,或者将函数对象作为参数传递给另一个函数,例如:
虽然我们说,函数3要素是函数的关键,但是Dart语言提供了极简的编程方式和极畅快的编程体验,因此无论是返回值的类型还是参数的类型,在定义函数时都可以省略。Dart会根据传入参数和return语句返回的值来自动匹配类型,对于开发者来说,这是一个非常好用的特性,例如:
如果你觉得上面定义函数的形式还不够简洁,没关系,Dart中还提供了箭头函数,若某个函数的函数体只有一句语句(就如上面的addFunc函数),则使用箭头函数只需要一行代码即可定义,示例如下:
num addFunc2(num a,num b)=>a+b;
也可以将参数类型和返回值类型省略,示例如下:
addFunc2(a,b)=>a+b;
箭头函数的格式如下:
返回值 函数名(参数)=>函数体语句
和普通函数一样,返回值和参数的类型都可以省略,并且唯一的一个函数体语句执行的结果会被当作返回值返回。
3.1.3 定义可选参数的函数
对于前面我们所编写的函数,在调用时,其传入的参数个数与类型必须与函数定义的一致。这种要求在实际开发中通常会稍显严格,有的时候,我们编写的函数的参数并非都是必须传入的,Dart函数提供位置可选参数与名称可选参数两种方式来定义可选参数的函数。
可选参数是指在调用函数时,某个参数是可选的,即调用者可以选择传入此参数,也可以选择不传入。在Dart中,可选参数分为名称可选参数和位置可选参数。对于名称可选参数,函数的定义格式如下所示:
返回值类型 函数名({参数列表}){ 函数体 }
和前面我们所学习的函数唯一不同的地方在于参数列表被放入了大括号中。对于名称可选函数,其返回值类型和参数类型也可以省略。下面是一个名称可选参数函数的示例:
muFunc函数中定义了两个参数,分别表示姓名和年龄,这两个参数都是可选的,在调用的时候,我们需要使用参数名加冒号的方式来进行参数的传递,参数的先后顺序并不重要,并且调用者也可以选择只传一部分参数或者不传参数,函数在执行时都不会产生错误。
位置可选参数函数是指函数某个位置的参数是可选的,即调用者可以选择传这个参数,也可以选择不传这个参数。位置可选参数函数更加简单,其只需要将可选的参数放入中括号中即可,例如:
myFunc2函数定义了两个参数,其中第一个参数是必选的,调用者必须传这个参数,表示年龄的age参数则是可选的,例如下面的代码调用这个函数完全正确:
myFunc2("珲少");
有了可选参数函数,我们编写Dart程序时将有更大的灵活性,这只是Dart中函数功能强大的一方面,在3.1.4节中,我们将介绍可选参数的默认值,可选参数配合默认值一起使用,不仅使函数非常灵活,也使其安全性得到了保证。
3.1.4 函数可选参数的默认值
可选参数的函数在调用时,如果调用者没有传入某个参数,函数中获取到的此参数值就为null,例如:
在定义可选参数函数时,我们也可以为这些可选的参数设置一个默认值,设置默认值后,如果调用者没有传入这个参数,函数内就会使用此参数的默认值。名称可选参数函数设置默认值的方式如下:
位置可选参数函数设置默认值的方式如下:
在定义可选参数函数时,我们应尽量为其提供一个默认值,默认值可以使函数的执行更加可控,程序的执行更加安全。
3.1.5 匿名函数
我们知道,通过函数名可以进行函数的调用,然而并非所有函数都有名字,在Dart中,没有名字的函数被称为匿名函数。匿名函数可以直接赋值给变量,通过变量来进行函数的调用,也可以通过匿名函数来创建自执行的函数,例如:
上面的示例代码中,func变量被赋值成一个函数对象,在main函数中使用func变量进行了函数的调用,main函数中还定义了一个自执行匿名函数。所谓自执行函数,是指函数的创建和执行一步完成,其和正常的函数一样,自执行函数可以有参数,也可以有返回值。其实对于自执行函数,除了可以将某些逻辑聚成一个代码块外,其更大的作用是会生成一个新的内部作用域,内部作用域可以防止外部变量对作用域内变量的污染,也可以防止在同一个作用域中产生太多的变量。关于作用域的相关内容在3.1.6小节介绍。
3.1.6 词法作用域
所谓作用域,其实指的就是变量的有效范围,在Dart中,大括号会生成作用域,也就是说,条件结构、循环结构、函数等都会生成作用域。在作用域内声明或定义的变量,出了作用域就被销毁并且无法使用,例如:
但是,外层作用域中的变量是可以在内层作用域中使用的,例如:
在Dart中,作用域是可以防止变量污染的,如果在内层作用域中声明了和外层作用域中名字一样的变量,内层作用域中就不能再使用外层作用域的变量,示例如下:
3.1.7 关于闭包
闭包也是函数,它是函数在一种特殊场景下的应用。3.1.6小节我们了解了词法作用域的基本知识,正常情况下,变量离开其作用域就会失效,但闭包是一种特殊情况。
闭包是一个函数对象,其特点是当变量离开其作用域后依然可以被函数内部使用,例如:
上面的示例代码中,func函数会返回一个匿名函数,这个匿名函数就是闭包,在调用函数时,我们传入了一个字符串参数name,仅从词法作用域来看,name变量只能在函数func内使用,出了作用域就会失效。在示例代码中,特殊的是func函数返回了一个新的函数,这个函数中对name变量进行了引用,这时这个新的函数就变成了闭包,其会对使用到的变量进行拷贝,即使func函数结束,离开了原name参数变量的作用域,闭包中引用的变量依然有效。
闭包是一种非常常用的语法结构,在实际开发中,我们也经常会使用到它。虽然闭包的概念略微有些难以理解,但是应用十分简单,我们可以在后面的实际应用中再深刻理解它。