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岁。');
};
注意:如果函数体中只有一行返回语句,我们可以使用胖箭头简写法。具体简写的的方式为:
- 在形参
()
和方法体{}
之前添加胖箭头=>
- 省略方法体的
{}
,并省略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;
};
}