函数 (Function)

本章节将介绍函数的基本概念,位置参数、命名参数、函数类型、匿名函数等内容,将在后续章节陆续介绍。我们已经多次见过main函数,它是程序的入口。抽象地说,函数对输入数据(输入参数列表)进行处理,然后输出其处理结果(返回值)。

flowchart LR
   a(( )) -->|输入参数| B(处理(函数体))
   B --> |返回值|c(( ))

先来看一个示例程序,计算平面上两点之间的欧式距离:

\( \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2} \)

//ch0204_1.dart
import 'dart:math';

typedef Point = ({double x, double y}); // 1

void main() {
  var a = (x: 1, y: 2), b = (x: 3, y: 4); // 2
  var distance = sqrt(
    (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y),
  ); // 3
  print('$a -> $b: $distance'); // 3a

  var c = (x: 6, y: 5); //4
  distance = sqrt((a.x - c.x) * (a.x - c.x) + (a.y - c.y) * (a.y - c.y)); //5
  print('$a -> $c: $distance'); // 5a
}

  1. 使用typedef创建一个类型别名Point,它代表了二维平面里的一个点;
  2. 声明并初始化2个Pointab;
  3. 计算ab间的欧式距离,随后打印 (3a);
  4. 声明并初始化Point c;
  5. 计算ac间的欧式距离,随后打印 (5a);

将计算欧式距离的代码,抽取成一个函数 distance,然后改写上例:

//ch0204_2.dart
import 'dart:math';

typedef Point = ({double x, double y}); 

void main() {
  Point a = (x: 1, y: 2), b = (x: 3, y: 4);
  print('$a -> $b: ${distance(a, b)}'); // 2

  Point c = (x: 6, y: 5);
  print('$a -> $c: ${distance(a, c)}'); // 3
}

double distance(Point a, Point b) { // 1
  final dx = a.x - b.x;
  final dy = a.y - b.y;
  return sqrt(dx * dx + dy * dy);
}
  1. 定义函数distance,它计算平面上两点间的欧式距离;
  2. 调用distance计算ab间的欧式距离;
  3. 调用distance计算ac间的欧式距离。

重构(改写)后的程序,功能不变,但更为简洁易读,消除了重复代码(计算欧式距离),并可对该代码块(distance 函数)进行单元测试(第8章内容)。

命名函数

通常,创建一个命名函数的语法为:

返回值的类型 函数名称(输入参数列表){
   函数体
}

对于上述 distance函数:

  • 返回值的类型: double;
  • 函数名称:distance
  • 输入参数列表:Point a, Point b
    • Point a 表示输入参数 a的数据类型为Point;
  • 花括号({})内的代码为函数体;
  • 使用return返回函数的返回值。

如果函数没有返回值,用void表示:

void printMe(String me) {
  print(me);
  // return;
}  

像此例中的return;,它位于函数体的最后一行,通常予以省略。

下面两点通常不被鼓励,除非你有充分的理由,因为它让Dart的静态类型分析失效:

  • 命名函数的返回值的类型,如果不写出来,就表示它是 dynamic (而不是void);
  • 类似地,命名函数的输入参数的类型,如果不写明,就代表dynamic

返回值

如果没有指定返回值,则语句 return null; 会被隐式附加到函数体中,函数将返回null

如果一个函数需要返回多个值,通常返回一个记录(Record)类型。

// ch0204_3.dart
(bool ok, String) foo() {
  // Do something
  return (true, 'done');
}

arrow 语法

如果函数体只有一个表达式,可以使用arrow(箭头)语法来简化。例如

int increase(int value) {
  return value + 1;
}

使用arrow语法简化为:

int increase(int value) => value + 1;

getter 与 setter

getter 与 settter 是一种特殊的函数,分别使用 getset 关键字定义 。

// ch0204_5.dart
var _foo = 1;

int get foo => _foo;

set foo(int value) {
  if (value > 0) {
    _foo = value;
  }
}

void main() {
  print('foo=$foo'); // Output: foo=1
  foo = 2;
  foo = -1;
  print('foo=$foo'); // Output: foo=2
}

此例中的foo在使用者看来,就像一个变量,但foo setter 给私有变量 _foo提供了额外的保护,它只将正数赋值给_foo

main函数

程序的入口main函数,返回void,有一个可选的参数,其类型为List<String>,用于命令行程序中。

// ch0204_4.dart
void main(List<String> args) {
  for (var arg in args) {
    print(arg);
  }
}

此程序打印命令行参数,一个参数一行。使用dart run运行它:

$ dart run bin/ch02/ch0204_4.dart hello dart
hello
dart

NOTE: 可使用 args 库来定义和解析命令行参数。

外部函数(external function)

外部函数的函数体(实现)与函数声明是分离的。外部函数的实现可以来自另一个Dart库,更常见的是,来自其它编程语言(例如C)。使用 external 声明一个外部函数,例如 Object类中的 toString() 方法:

  external String toString();

小测验

下例中的printMe2的返回类型是什么?实际上它返回了什么?参数me的类型又是什么?

printMe2(me) {
  print(me);
}

参考资料