函数 (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
}
- 使用
typedef
创建一个类型别名Point
,它代表了二维平面里的一个点; - 声明并初始化2个
Point
,a
和b
; - 计算
a
、b
间的欧式距离,随后打印 (3a); - 声明并初始化
Point c
; - 计算
a
、c
间的欧式距离,随后打印 (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);
}
- 定义函数
distance
,它计算平面上两点间的欧式距离; - 调用
distance
计算a
、b
间的欧式距离; - 调用
distance
计算a
、c
间的欧式距离。
重构(改写)后的程序,功能不变,但更为简洁易读,消除了重复代码(计算欧式距离),并可对该代码块(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 是一种特殊的函数,分别使用 get
与 set
关键字定义 。
// 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);
}