dart入门潜修基础篇之方法

本文收录于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
}

需要注意的是,箭头语法只能在特定环境下使用,其使用条件必须同时满足以下条件:

  1. 方法体中只有一条语句。
  2. 该语句必须是一个表达式。

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方法
}

上面代码展示了方法作为一等公民的“特权”,对于上面的代码,我们可以做以下总结:

  1. dart中的方法确实如同一般变量一样,可以自由的赋值、传参。
  2. 当方法作为参数传入另一个方法时(姑且称之为A方法),我们可以将A方法称为高阶方法。
  3. 只要接收方法类型作为入参的方法,我们才能将方法实例传入。比如上面的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));//调用该方法

至此,本篇文章阐述完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值