Dart

10.Dart基础-函数(二)

内容纲要

1. main 函数

1.1 main 函数的基本格式

在每个 Dart 程序中,必须提供一个 main() 函数,作为整个应用程序执行的入口。而且,main() 函数的返回值必须是 void,否则会报错。示例代码如下:

void main() {
  print('ok');
}

如果提供的 main() 函数的返回值不是 void,则会编译报错,所以下面的声明的 main() 函数是错误的:

String main() {
  // 这个 main 函数是错误的,因为返回值必须是 void
}

1.2 main 函数的形参

在声明 main() 函数时,还可以在形参中接收一个 List<String> 类型的可选参数。通过这个参数,我们可以获取到用户执行 dart 可执行文件 命令时额外传递的参数项,示例代码如下:

// functions.dart 文件

// 在定义 main 函数时,声明参数 args 来接收用户额外传递的参数项
void main(List<String> args) {
  // 打印参数项
  print(args);
}

如果用户在执行 dart 命令时没有提供任何参数,则打印的 args 为空数组 []

dart .\functions.dart

如果用户提供了参数,则打印的 args 为字符串数组 [-h, -v]

dart .\functions.dart -h -v

为了方便起见,推荐使用参数库定义解析命令行中的额外参数项

2. 函数是一级对象

在 Dart 中,函数本质上也是对象,函数的类型是 Function,因此,可以把函数作为参数传递给另外一个函数。示例代码如下:

// 1. 定义一个 show 方法,包含两个参数
// 参数1:年龄
// 参数2:回调函数
void show(int age, Function func) {
  if (age >= 18) {
    print('$age岁,');
    func();
  } else {
    print('$age岁,未成年!');
  }
}

void main(List<String> args) {
  // 2. 走 else 分支
  show(16, () => print('抽烟'));
  // 3. 走 if 分支
  show(18, () => print('烫头'));
}

上述代码中的 () => print('烫头') 是匿名函数,下一节会对其进行介绍。

3. 匿名函数

大多数方法都有自己的名称,例如 main 方法,或是其它自定义的方法,例如:

// 1. 这里的 main 是一个有名字的函数
void main(List<String> args) {
  // 通过方法名 show 调用指定的方法
  show();
}

// 2. 这里的 show 也是一个有名字的函数
void show() {
  print('ok');
}

在 Dart 中还可以创建没有具体名称的方法,它们经常被叫做匿名函数Lambda 表达式Closure 闭包。匿名函数的基本语法格式如下:

(形参列表) {
  函数体;
};

我们还可以把匿名函数赋值给一个变量,然后基于变量名调用这个匿名函数,示例代码如下:

void main(List<String> args) {
  // 2. 变量 show 指向的是一个匿名函数,
  // 因此可以通过 () 的方式调用这个函数,就像调用其它函数一样
  show(18, '刘龙彬');
}

// 1. 定义匿名函数,并通过 = 把匿名函数的引用赋值给变量 show
var show = (int age, String name) {
  print('大家好,我是$name,我今年$age岁。');
};

注意:如果函数体中只有一行返回语句,我们可以使用胖箭头简写法。具体简写的的方式为:

  1. 在形参 () 和方法体 {} 之前添加胖箭头 =>
  2. 省略方法体的 {},并省略 return 关键字

示例代码如下:

void main(List<String> args) {
  // 1. 定义 list 数组
  var list = ['liulongbin', 'escook', 'nibaba'];

  // 2. 调用数组的 map 函数生成新集合
  // 传递给 map 函数的参数是一个“完整形式的匿名函数”
  var res1 = list.map((item) {
    return item.toUpperCase();
  }).toList();

  // 3. 输出 [LIULONGBIN, ESCOOK, NIBABA]
  print(res1);

  // ----

  // 4. 调用数组的 map 函数生成新集合
  // 传递给 map 函数的参数是一个“简写形式的箭头函数”
  var res2 = list.map((item) => item.toUpperCase()).toList();

  // 5. 输出 [LIULONGBIN, ESCOOK, NIBABA]
  print(res2);
}

注意:在实际开发中,合理使用箭头函数能极大精简代码的结构

4. 返回值

所有函数都有返回值,没有显示声明 return 语句的函数,默认返回 null。示例代码如下:

void main(List<String> args) {
  var res1 = foo(1, 2);
  var res2 = boo(3, 4);

  // 输出 3
  print(res1);
  // 输出 null
  print(res2);
}

// 1. 定义函数 foo
// 返回值是 int 类型
var foo = (int a, int b) {
  return a + b;
};

// 2. 定义函数 boo
// 没有 return 语句,默认返回 null
var boo = (int a, int b) {
  print('两个数相加的和是${a + b}');
};

5. 词法作用域

在 Dart 中,变量的作用域在编写代码的时候就已经确定下来了。大括号内定义的变量只能在大括号内部访问。示例代码如下:

var v1 = 1;

// 1. 在 main 函数内,可以访问到变量 v1 和 v2
// 无法访问变量 v3 和 v4,因为 v3 属于 f1 函数,v4 属于 f2 函数
void main(List<String> args) {
  var v2 = 2;

  // 2. 在 f1 函数内,可以访问到变量 v1、v2、v3
  // 无法访问变量 v4,因为 v4 属于 f2 函数
  void f1() {
    var v3 = 3;

    // 3. 在 f2 函数内,可以访问到变量 v1、v2、v3、v4
    void f2() {
      var v4 = 4;
    }
  }
}

访问词法作用域中的变量时,会遵循就近原则:如果内层作用域的变量和外层作用域的变量名相同,则访问到的是内层作用域的变量。示例代码如下:

// 1. 外层作用域
var v1 = 1;
var v3 = 3;
void main(List<String> args) {
  // 内层作用域
  var v1 = 111;
  var v2 = 2;

  print(v1); // 输出 111,访问的是内层作用域的变量 v1
  print(v2); // 输出 2,访问的是外层作用域的变量 v2
  print(v3); // 输出 3,访问的是外层作用域的变量 v3
}

6. 词法闭包

闭包的概念:某个函数,即使在它原始作用域之外的地方调用,这个函数依然能够访问在它词法作用域内的变量。

通俗的理解就是:在函数 A 内部 return 了一个函数 B,哪怕函数 A 的调用结束了,函数 B 依然能访问函数 A 词法作用域内的变量。

示例代码如下:

void main(List<String> args) {
  // 2. 调用函数 getFn,调用的结果是返回一个匿名函数,并把这个匿名函数赋值给变量 fn
  // 由于 fn 引用了父作用域中的变量 count,形成了闭包,因此父作用中的 count 不会被销毁
  var fn = getFn();

  // 3. 每次执行函数 fn,都会先让闭包中的 count 自增 +1,再把 +1 的结果 return 出来
  print(fn()); // 输出 1
  print(fn()); // 输出 2
  print(fn()); // 输出 3
}

// 1. 定义函数 getFn,内部 return 了一个匿名函数
Function getFn() {
  // 1.1 函数 getFn 词法作用域内声明了变量 count
  int count = 0;

  return () {
    // 1.2 匿名函数引用了外层词法作用域中的变量 count
    return ++count;
  };
}

一个不再讲课の前端程序员。

留言

您的电子邮箱地址不会被公开。 必填项已用*标注