本文收录于dart入门潜修系列教程。
创作不易,转载还请备注。
方法
上篇文章我们提到过,在dart中一切都是对象,方法也不例外,方法也是个对象,也就是说dart中的方法是“一等公民”。这意味着我们可以将方法赋值给一个变量,也可以将方法作为参数传入到另一个方法中。实际上,在dart中,方法对应的类型是Function类。先来看下方法的简单例子:
void main() {
print("hello world!");
}
没错,这个就是我们非常熟悉的应用程序的执行入口main方法。从main方法可知,dart中的方法和其他语言一样,有返回值+方法名+方法参数+方法体组成。在方法体中,如果有返回值则需要显示使用return语句进行返回,否则会返回默认值null,如下所示:
//计算两个整数的和
int sum(int x, int y) {
int sum = x + y;//注意,此方法没有返回值
}
//计算两个整数的和
int sum2(int x, int y) {
int sum = x + y;
return sum;//显示指定了方法返回值
}
//测试
void main() {
print(sum(1, 2));//打印null
print(sum2(1, 2));打印3
}
上面例子演示了在dart中方法返回值的不同场景,因为在dart中一切即对象,所以除了void类型,其他类型如果不显示指定其返回值,默认都返回null。
但dart在方法定义的时候可以省略其返回类型,此时将会由return语句决定该方法返回什么类型,如下所示:
//定义一个方法,没有指定该方法的返回值类型
sum(int x, int y) {
return x + y;//该方法的返回类型将由return语句决定,即返回int
}
//测试
void main() {
print(sum(1, 2));//打印3
}
如果在定义方法的时候没有指定返回类型,并且方法体也没有调用return语句进行显示返回,那么该方法依然会返回默认值null,而不是void。
在dart中,还支持箭头语法,即对于方法体中只有一个表达式的方法,我们可以使用箭头语法,如下所示:
//使用箭头语法
sum(int x, int y) => x + y;
//测试
void main() {
print(sum(1, 2));//打印3
}
需要注意的是,箭头语法只能在特定环境下使用,其使用条件必须同时满足以下条件:
- 方法体中只有一条语句。
- 该语句必须是一个表达式。
dart中的方法支持可选参数,示例如下:
//我们定义了一个合并两个字符串的方法combiningStr
//但是第二个字符串是可选的,如果没有传则返回第一个字符串
String combiningStr(String firstStr, [String secondStr]) {
String result = firstStr;
if(secondStr != null){
result += secondStr;
}
return result;
}
//测试
void main() {
print(combiningStr("hello"));//可以省略第二个参数
print(combiningStr("hello", " world"));//两个参数都传
}
打印结果如下:
hello
hello world
上面例子演示了dart中可选参数的用法,可选参数的语法是使用中括号[ ]包裹即可。
既然讲到了可选参数,我们再回头看下dart中的程序入口main方法,我们知道类似于java等语言,其main方法都接收入参,用于接收命令行参数,那么dart中如果也有命令行参数该如何处理呢?
看过可选参数这一部分之后,想必大家已经能想到了,main方法实际上也是有参数的,只不过这个参数是可选的,当有命令参数的时候就会触发,其完整的定义如下所示:
void main(List<String> args) {
}
dart还支持命名参数,即在传入参数的时候指定传递给那个参数,依然是上面的例子,我们还可以这么做,如下所示:
//显示设置入参的flag,即使用{}包括起来
String combiningStr({String firstStr, String secondStr}) {
String result = firstStr;
if (secondStr != null) {
result += secondStr;
}
return result;
}
//测试
void main() {
//调用的时候我们就可以显示指定flag来指定传参
print(combiningStr(firstStr: "hello", secondStr: " world"));
}
此时,如果我们只传入一个参数也是合法的,只不过我们要确保方法体内部对null的处理,以避免crash,比如我们可以这么调用:
//只显示指定传入secondStr,没有传入firstStr
print(combiningStr(secondStr: " world"));
注意,命名参数和可选参数无法同时存在。
从上面的代码可知,如果我们采用命名参数就会存在一个问题,那就是需要处理没有传入参数时的情况。比如,上面我们就可以选择只传secondStr,而不传firstStr,此时如果firstStr是combiningStr方法必须的参数,那么就可能会产生未知的错误,这个是很不好的体验,针对这种情况,dart为我们提供了@required注解,用于标注哪些参数必须要传入,如下所示:
import 'package:meta/meta.dart';
//我们使用@required注解来标明firstStr为必传参数
String combiningStr({ @required String firstStr, String secondStr}) {
String result = firstStr;
if (secondStr != null) {
result += secondStr;
}
return result;
}
//测试方法
void main() {
print(combiningStr(secondStr: " world"));//!!!错误,不能省略参数firstStr
print(combiningStr(firstStr: "hello", secondStr: " world"));//正确
}
需要注意的是@required注解并没有在dart标准库中,需要单独安装(位于meta包中),刚好阐述到这一部分,我们就来看下如何在dart工程中引入新包。
dart工程使用YAML文件来管理包依赖,yaml并不是本篇文章的阐述重点,所以下面直接来说下如何使用。
首先在我们的工程父目录中新建yaml文件(名字可随意取),然后在yaml文件中引入meta包:
name: dart_lean
dependencies:
meta: ^1.1.7
最后我们在使用的地方import即可,如下所示:
import 'package:meta/meta.dart';
这样我们就可以使用meta包暴露的接口了。
同其他语言一样,dart也支持方法参数拥有默认值,可选参数和命名参数都可以定义默认值,如下所示:
//我们为firstStr指定了默认值“hello"
String combiningStr({String firstStr = "hello", String secondStr}) {
String result = firstStr;
if (secondStr != null) {
result += secondStr;
}
return result;
}
//测试方法
void main() {
print(combiningStr(secondStr: " world"));//打印 hello world
}
在dart的旧版本中,支持使用冒号(:)语法来指定默认值,但是新的版本也不建议这么用,而是使用等于号(=) 来替代冒号。
方法是一等公民
前面提到了dart中的方法是一等公民,也解释了什么是一等公民,本节我们来看下几个例子,如下所示:
//我们定义了一个打印整型元素的方法printIntElement
void printIntElement(int element) {
print(element);
}
//测试
void main(List<String> args) {
var list = [1, 2, 3];//生成一个整型列表
var myPrint = printIntElement;//我们可以将方法赋值给一个变量
list.forEach(myPrint);//然后将该方法地址传递给forEach方法
}
上面代码展示了方法作为一等公民的“特权”,对于上面的代码,我们可以做以下总结:
- dart中的方法确实如同一般变量一样,可以自由的赋值、传参。
- 当方法作为参数传入另一个方法时(姑且称之为A方法),我们可以将A方法称为高阶方法。
- 只要接收方法类型作为入参的方法,我们才能将方法实例传入。比如上面的forEach方法,实际上它本身接收的就是个方法类型,其定义如下所示:
//forEach实际上接收的就是一个返回值为void方法类型
void forEach(void action(E element)) {}
匿名方法
匿名方法大家一定非常熟悉了,充斥在各个语言当中。匿名方法,故名思议就是没有名字的方法。其实和匿名方法相提并论的还有lambda表达式、闭包等,关于这些概念我曾在我的另一个系列文章中阐述过,具体可以参见kotlin入门潜修之进阶篇—高阶方法和lambda表达式这篇文章。下面来看下dart中匿名方法,先来看下其定义(同其他语言一样):
([[Type] param1[, …]]) {
codeBlock;
};
看上去除了没有方法名字之外,和命名方法一模一样,确实如此,接下来我们看一个匿名方法使用的具体案例,这个案例使用的正是上述我们阐述过的list的forEach方法,即我们不再提供显示的打印方法,而是采用匿名方法实现打印功能,如下所示:
void main(List<String> args) {
var list = [1, 2, 3];
//forEach的入参就是匿名方法
list.forEach((item){
print(item);
});
}
由于上述匿名方法只有一条语句,我们还可以简化如下:
list.forEach((item) => print(item));
作用域
很多语言都有作用域,所谓作用域就是位于其中的代码只能在特定的范围内生效,外界无法访问到,dart同样不例外,也有自己的作用域。来看个例子:
var topLevelStr = "I am top Level str";//顶层作用域
void main(List<String> args) {//main方法作用域
var mainScopeStr = "I am in main function : $topLevelStr";
void inMain() {//inMain方法作用域
var inMainScopeStr = "I am in inMain function : $mainScopeStr : $topLevelStr";
void inInMain() {//inInMain方法作用域
var inInMainScopeStr = "Iam in inInMain function : $mainScopeStr : $inMainScopeStr : $topLevelStr";
}
}
}
由示例可以看出,里层的作用域可以访问其外层的任何一个作用域,但是外层作用域却无法访问内层的作用域。
在dart中,这种作用域被称为词法作用域,即变量的书写位置决定了其作用范围。
闭包
什么是闭包?在kotlin入门潜修之进阶篇—高阶方法和lambda表达式这篇文章中,我曾从作用域的视角阐述什么是闭包,这里基于上述词法作用域再阐述下。
首先,闭包是基于词法作用域的,那些可以访问其词法作用域中(包括其外层作用域)的变量的方法对象都可称之为闭包。
来看个闭包的例子:
//定义一个接收int入参的方法sum,其返回值是个匿名方法,
//该匿名方法同样接收一个int型的入参
sum(int firstNum) {
return (int secondNum) => firstNum + secondNum;
}
//测试
void main() {
print(sum(1)(2));//打印'3'
}
上面代码演示了闭包的使用,在sum方法中,我们返回了一个匿名方法对象,该匿名方法对象使用了其外层的变量,即sum方法的入参firstNum,最终完成两个数的相加
在这个过程中,sum(1)其实就是一个闭包,由于其返回值就是上面我们提到的那个匿名方法,所以我们还可以继续像调用方法一样,继续调用sum(1),来完成最终的计算,即sum(1)(2)。如果不太明白这种写法,我们完全可以将其拆分出来,表示如下:
var tempSum = sum(1);//返回一个tempSum方法对象
print(tempSum(2));//调用该方法
至此,本篇文章阐述完毕。
网友评论