下面是一个简单的函数示例:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
虽然返回值类型也可以省略,但建议加上,可读性更好,省略后的代码如下:
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
如果函数体 只有一条语句,也可以使用 箭头函数:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
其中,=> expr;
语法就是 { return expr; }
的简写形式,与其它语言不同,如果函数体有多条语句,就不能使用这种语法了。
1. 参数
一个函数可以包含任意数量的 必选位置参数(required positional parameters
),这些参数后面可以跟随 命名参数(named parameters
)或 可选位置参数(optional positional parameters
)(但二者不可同时使用)。
1.1 命名参数
定义函数时,使用 {param1, param2, …}
语法来声明命名参数,命名参数默认可选,但可以通过 required
显式标记为必选。若没有标记 required
,也没有指定默认值,则其类型必须为可空类型(因为可空类型的默认值为 null
)。
// 声明
void configure({
int? width, // 可选参数,默认为null
int height = 10, // 可选参数,默认为10
required String title // 必需参数,必须显式传递
}) {}
// 调用
configure(width: 10, title: "标题");
函数声明时,命名参数必须在必选位置参数之后,但调用时,命名参数可以在参数列表的任意位置。
// 声明
void exampleFunction(int x, {int? y, int z = 10}) {}
// 调用
exampleFunction(1, y: 2);
exampleFunction(z: 10, y: 2, 1);
1.2 可选位置参数
用方括号 []
包裹起来的一组函数参数为可选位置参数,若未提供默认值,则其类型必须为可空类型(因为可空类型的默认值为 null
):
// 声明
void enableFlags(
bool bold, [ // 必选位置参数
bool = true, // 可选位置参数,默认值为true
bool? underline, // 可选位置参数,默认值为null
]) {}
// 调用
enableFlags(true); // 只传必需参数
enableFlags(true, false); // 传递第一个可选参数
enableFlags(true, false, true); // 传递所有可选参数
2. main() 函数
每个应用都必须拥有一个顶级 main()
函数,该函数作为应用程序的入口点。main()
函数返回 void
类型,并且可选择接收一个 List<String>
类型的参数用于传递命令行参数。
// 基础形式
void main() {
print('应用启动');
}
// 带参数的形式
void main(List<String> arguments) {
print('传入参数: $arguments');
}
3. 一等函数
在 Dart
中,函数是一等对象,享有与其他数据类型(如整数、字符串、对象)同等的地位和权利,同样可以被作为参数传递、为变量赋值、作为函数返回值、存储在数据结构中(如列表、映射)等。
// 1. 接受函数作为参数
void executeTask(String task, Function() callback) {
print('开始: $task');
callback(); // 执行传入的函数
print('结束');
}
executeTask('下载', () { // 传递一个函数(这里是匿名函数)作为参数
print('正在下载文件...');
});
// 2. 将一个函数赋值给一个变量
var shout = (String msg) => '${msg.toUpperCase()}!';
// 3. 返回一个函数
Function multiplier(num factor) {
return (num value) => value * factor;
}
// 4. 将函数存入Map
var operators = {
'add': (a, b) => a + b,
'subtract': (a, b) => a - b,
};
print(operators['add']!(5, 3)); // 输出: 8
4. 函数类型
函数类型是通过将 函数声明头 中的函数名替换为关键字 Function
而获得的。此外,位置参数的名称可以省略(也可以不省略),但命名参数的名称不可省略。
void greet(String name, {String greeting = 'Hello'}) =>
print('$greeting $name!');
// greet替换成了Function,省略了位置参数的名称 name,命名参数名称 greeting 不可省略
void Function(String, {String greeting}) g = greet;
g('Dash', greeting: 'Howdy');
5. 匿名函数
虽然大部分情况下会给函数命名(如 main()
或 printElement()
),但有时也可以创建没有名称的函数,这类函数被称为匿名函数、lambda 表达式 或 闭包。基本结构如下:
([[Type] param1[, ...]]) {
// 代码块;
}
以下是一个简单的例子:
const list = ['apples', 'bananas', 'oranges'];
var uppercaseList = list.map((item) {
return item.toUpperCase();
}).toList();
for (var item in uppercaseList) {
print('$item: ${item.length}');
}
如果匿名函数只有一条表达式或 return
语句,也可以简化为箭头函数(lambda 表达式):
var uppercaseList = list.map((item) => item.toUpperCase()).toList();
uppercaseList.forEach((item) => print('$item: ${item.length}'));
6. 闭包
6.1 词法作用域
Dart
根据代码的布局结构确定变量的作用域。具备这种特性的编程语言称为词法作用域语言。可以通过 "沿花括号向外查看" 的方式判断变量是否在作用域内。
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}
嵌套函数 nestedFunction()
可以访问从当前作用域直至最顶层的每一级变量,反之,外层则不能访问里层的变量。
6.2 闭包
当一个函数对象在其词法作用域之外运行时,仍能访问该作用域内的变量,这样的函数被称为闭包。闭包主要解决的是让局部变量常驻内存,但又不像全局变量一样污染全局环境的问题。
在以下示例中,makeAdder()
捕获了变量 addBy
。无论返回的函数被传递到何处,它都会始终记住 addBy
的值:
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
6.3 函数提取(Tear-offs)
当引用函数、方法或命名构造函数时不加括号,Dart
会自动创建函数提取(tear-off
)。这是一种特殊的闭包:它接受与原函数相同的参数,并在被调用时执行底层函数。如果代码需要创建一个闭包来调用与闭包参数列表完全相同的命名函数,请勿使用 lambda
表达式包装调用,而应直接使用函数提取。
var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();
// 函数提取
charCodes.forEach(print);
// 方法提取
charCodes.forEach(buffer.write);
虽然用法上等价于如下的 lambda
表达式包装调用,但并不推荐。
charCodes.forEach((code) {
print(code);
});
charCodes.forEach((code) {
buffer.write(code);
});
7. 返回值
所有函数都会返回一个值,若未明确指定返回值,系统会自动在函数体末尾隐式添加 return null;
语句。
foo() {}
assert(foo() == null);
如果函数需要返回多个值,可以使用记录类型:
(String, int) foo() {
return ('something', 42);
}
8. 生成器
当需要延迟生成一个值序列时,可以考虑使用生成器函数。Dart
原生支持两种生成器函数:
- 同步生成器:返回一个
Iterable
对象 - 异步生成器:返回一个
Stream
对象
要实现同步生成器函数,只需将函数体标记为 sync*
,并使用 yield
语句来生成值即可:
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
要实现异步生成器函数,只需将函数体标记为 async*
,并使用 yield
语句来生成值即可:
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
若生成器采用递归实现,使用 yield*
可以有效提升其性能。
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
9. 外部函数
外部函数是一种函数体与其声明分离的函数,在函数声明前添加 external
关键字即可定义,如下所示:
external void someFunc(int i);
外部函数的实现通常来自其他编程语言(如C
、JavaScript
等),使其能够在 Dart
代码中使用,具体实现和使用方式高度依赖特定平台。
相关推荐
- Dart 语法要点(1) —— 注释、变量、常量、命名规则、数据类型 2025-09-14
- Dart 开发环境搭建 2025-09-12
评论0
暂时没有评论