循环
这里展示了如何使用循环和支持语句来控制Dart代码流:
- for 循环
- while 和 do while 循环
- break 和 continue
你可以使用以下关键字操作Dart中的控制流:
- 分支,比如 if 和 switch
- 异常,比如 try、catch 和 throw
for 循环
你可以使用标准的 for 循环进行迭代。例如:
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
print(message); // 打印 Dart is fun!!!!!
Dart 语言中的 for 循环内部的闭包会捕获索引的值。这避免了在 JavaScript 中常见的一个陷阱。例如,考虑以下情况:
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i)); // 存储匿名函数(闭包)
}
for (final c in callbacks) {
c(); // 循环每个匿名函数(闭包)时,匿名函数中的i都保存了对应的索引值
}
输出结果是预期的0和1。相比之下,在 JavaScript 中,同样的例子会打印出两个2。
有时在遍历像 List 或 Set 这样的 Iterable 类型时,你可能不需要知道当前的迭代计数器。在那种情况下,为了编写更简洁的代码,可以使用 for-in 循环:
var lists = [0, 1, 2];
for (final num in lists) {
print(num); // 分别打印 0、1、2
}
要处理从 Iterable 对象获得的值,还可以在for-in循环中使用模式:
class User {
User(this.name, this.age);
final String name;
final int age;
}
void main() {
List<User> lists = [User("张三", 20), User("李四", 18)];
for (final User(:name, :age) in lists) {
print('姓名:$name 年龄:$age.');
}
}
小提示
要练习使用for-in循环,请遵循 Iterable 集合教程。
Iterable 类还提供了一个 forEach() 方法作为另一种选择:
var collection = [1, 2, 3];
collection.forEach(print); // 1 2 3
while 和 do-while 循环
while循环在循环体执行之前判断条件:
// 先判断是否满足条件,满足才调用doSomething()函数,以此循环下去
while (!isDone()) {
doSomething();
}
do while循环在循环体执行之后才判断条件:
// 先调用printLine()函数再判断是否满足下一次循环条件
do {
printLine();
} while (!atEndOfPage());
break 和 continue
使用 break 来停止循环:
while (true) {
// 当shutDownRequested()函数返回true时,通过break关键字跳出while循环,不再调用processIncomingRequests()函数
if (shutDownRequested()) break;
processIncomingRequests();
}
使用 continue 来跳过当前循环迭代并继续下一次循环:
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
// 当运行continue关键字时,本次循环到处终止(不运行本次的candidate.interview()),执行下一次循环
continue;
}
candidate.interview();
}
如果你使用的是 list 或 set的 Iterable ,下面是使用 where 和 forEach 两个关键字的另一种循环方式,where 负责条件判断,forEach 负责将 where 成立的条件的元素遍历出来:
List<User> lists = [User("张三", 20), User("李四", 18)];
lists.where((user) {
// 显示年龄小于19岁的用户
return user.age < 19;
}).forEach((user) {
print('姓名:${user.name} 年龄:${user.age}.');
});
分支
这个页面展示了如何在Dart代码中使用分支来控制流程:
- if 语句和元素
- if-case 语句和元素
- switch 语句和表达式
你可以使用以下关键字操作Dart中的控制流:
- 循环,比如 for 和 while
- 异常,比如 try、catch 和 throw
if 语句
Dart支持带有可选 else 子句的 if 语句。if 后面括号中的条件必须是一个计算结果为布尔值的表达式:
if (isRaining()) { // 是否下雨
you.bringRainCoat(); // 带雨衣
} else if (isSnowing()) { // 是否下雪
you.wearJacket(); // 穿外套
} else {
car.putTopDown();
}
要学习如何在表达式上下文中使用 if,请参阅前面的条件表达式。
if-case 语句
Dart的 if 语句支持在 case 子句后跟随模式进行匹配,下面介绍几种模式。
匹配数组模式:
var pair = [100, 101];
// 判断 pair 是否是一个长度为2并且是整型的数组
if (pair case [int x, int y]) {
print('坐标数组 $x,$y');
} else {
print('无效的坐标');
}
匹配记录模式:
(int, int) pair = (100, 101);
// 判断 pair 是否是一个记录并且参数类型都是整型和参数个数是两个
if (pair case (int x, int y)) {
print('坐标数组 $x,$y');
} else {
print('无效的坐标');
}
匹配字符串模式:
String p = "100";
// 判断 pair 是否字符串
if (p case String p1) {
print('字符串内容 $p1');
} else {
print('无效的字符串内容');
}
匹配模式不止上面介绍的三种,你可以使用任何类型的匹配模式。
if-case 语句提供了一种与单个模式进行匹配和解构的方法。要测试一个值是否匹配多个模式,请使用 switch 语句。
版本提示!
if 语句中的 case 子句需要至少 3.0 版本的语言。
switch 语句
switch 语句将一个值表达式与一系列 case 进行比较。每个 case 子句都是要匹配的值的模式。对于 case,你可以使用任何类型的模式。
当值匹配到某个 case 的模式时,该 case 的主体部分将会执行。非空的 case 子句在执行完毕后,会跳转到 switch 语句的末尾。它们不需要 break 语句。结束非空 case 子句的其他有效方式包括 continue、throw 或 return 语句。
当没有匹配的 case 子句时,使用 default 或通配符 _ 子句来执行代码。
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
case 'PENDING':
executePending();
case 'APPROVED':
executeApproved();
case 'DENIED':
executeDenied();
case 'OPEN':
executeOpen();
default:
executeUnknown();
}
空的 case 会贯穿到下一个 case,使得多个 case 可以共享同一个主体部分。对于不希望贯穿的空 case,可以在其主体部分使用 break 语句。对于非顺序的贯穿,你可以使用 continue 语句和标签:
提示!
“空的 case”指的是一个不包含任何执行语句的 case 子句。
var command = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();
continue newCase; // 继续在 newCase 标签处执行
case 'DENIED': // 空的 case 发生了贯穿
case 'CLOSED':
executeClosed(); // 对 DENIED 和 CLOSED 都运行
newCase:
case 'PENDING':
executeNowClosed(); // 对 OPEN 和 PENDING 都运行
}
// command如果是'OPEN'的话,将会调用executeOpen()和executeNowClosed()方法
你可以使用 逻辑或模式 来让多个 case 共享同一个主体或守卫条件。要了解更多关于模式和 case 子句的信息,请查阅关于 Switch语句和表达式的模式文档。
switch表达式
switch 表达式根据匹配到的 case 的表达式主体来生成一个值。您可以在 Dart 允许表达式的任何地方使用 switch 表达式。但不能在表达式语句的开头使用。例如:
var x = switch (y) { ... };
print(switch (x) { ... });
return switch (x) { ... };
如果你想在表达式语句的开头使用 switch,请使用 switch 语句。
switch 表达式允许您像这样重写 switch 语句:
var academic = 75;
var result = '';
switch (academic ) {
case 10 || 20 || 30 || 40: // 逻辑或模式
result = '成绩低于或者等于40';
case 50 || 60: // 逻辑或模式
result = '成绩低于或者等于60';
case >= 70 && <= 100: // 关系与逻辑与模式
result = '成绩在70至100之间';
default:
throw FormatException('成绩格式不正确');
}
print(result); // 打印 成绩在70至100之间
转化成一个表达式,像这样:
var academic = 75;
var result = switch (academic) { // 将结果赋值给 result 变量
10 || 20 || 30 || 40 => '成绩低于或者等于40',
50 || 60 => '成绩低于或者等于60',
>= 70 && <= 100 => '成绩在70至100之间',
_ => throw FormatException('成绩格式不正确')
};
print(result); // 打印 成绩在70至100之间
switch 表达式的语法与 switch 语句的语法不同:
- case 并不是以 case 关键字开始的(即每个 case 模式不需要写 case 关键字,而是直接写 case 表达式)。
- case 主体是单个表达式,而不是一系列语句(单行代码)。
- 每个 case 都必须有主体;对于空的 case,不存在隐式的贯穿(即不会自动执行下一个 case 的代码)。
- case 模式与其主体之间使用 => 进行分隔,而不是使用冒号 : 。
- 各个 case 之间使用逗号 , 进行分隔(并且允许在最后一个 case 后面有一个可选的尾随逗号)。
- 默认的 case 只能使用下划线 _ ,而不允许同时使用 default 和 _ 。
版本提示!
switch 表达式要求语言版本至少为 3.0。
穷尽性检查
穷尽性检查是一种特性,如果某个值有可能进入 switch 语句但无法匹配任何 case,则会在编译时报错。
// 对于可能为null的bool?类型的非穷尽性switch语句,缺少匹配null可能性的case:
bool? b;
switch (b) {
case true:
print('yes');
case false:
print('no');
// 可以在这里添加匹配null的 case null: 语句或者使用 default 或 _
}
一个 default case(default 或 _ )涵盖了可以流经 switch 语句的所有可能值。这使得对于任何类型的 switch 语句都是穷尽的。
枚举和密封类型在 switch 语句中特别有用,因为即使不使用 default 情况,它们的可能值也是已知且可以完全枚举的。对类使用密封修饰符,可以在对该类的子类型进行 switch 操作时启用穷尽性检查:
/// 形状
sealed class Shape {}
/// 正方形
class Square implements Shape {
final double length;
Square(this.length);
}
/// 圆形
class Circle implements Shape {
final double radius;
Circle(this.radius);
}
/// 计算面积
double calculateArea(Shape shape) {
// switch 传入的是 Shape 类型,则 switch 的 case 模式必须补齐它的
// 所有子类(枚举类型也一样),否则会出现编译错误
return switch (shape) {
Square(length: var l) => l * l,
Circle(radius: var r) => pi * r * r, // 圆周率需要引入 import 'dart:math';
};
}
void main() {
var value = calculateArea(Circle(10));
print("圆面积:$value");
}
如果任何人要添加一个新的 Shape 子类,这个 switch 表达式将是不完整的。穷尽性检查会告知你缺失的子类型。这允许你以某种函数式代数数据类型风格来使用Dart。
守卫子句
要在 case 子句之后设置一个可选的守卫子句,请使用 when 关键字。守卫子句可以跟在 if case 之后,并且既适用于 switch 语句,也适用于 switch 表达式。
var generalName = "关羽"; // 武将名字
var gyPassFirst = true; // 关羽通过第一关
var gyPassSecond = true; // 关羽通过第二关
var zfPassFirst = true; // 张飞通过第一关
var zfPassSecond = true; // 张飞通过第二关
// Switch 语句:
switch (generalName) {
case "关羽" when gyPassFirst && gyPassSecond: // 当匹配case模式后,再进一步判断when后面的约束,约束条件必须是布尔类型的值或者表达式(例如 1==1)
print("Switch 语句:关羽通关");
case "张飞" when zfPassFirst || zfPassSecond:
print("Switch 语句:张飞通关");
}
// Switch 表达式:
var value = switch (generalName) {
"关羽" when gyPassFirst && gyPassSecond => "关羽通关", // 当匹配case模式后,再进一步判断when后面的约束,约束条件必须是布尔类型的值或者表达式(例如 1==1)
"张飞" when zfPassFirst || zfPassSecond => "张飞通关",
_ => "输入的武将名字不存在",
};
print("Switch 表达式:$value");
// If-case 语句:
if (generalName case "关羽" when gyPassFirst && gyPassSecond) { // 当匹配case模式后,再进一步判断when后面的约束,约束条件必须是布尔类型的值或者表达式(例如 1==1)
print("If-case 语句:关羽通关");
} else if (generalName case "张飞" when zfPassFirst || zfPassSecond) {
print("If-case 语句:张飞通关");
}
守卫在匹配后计算任意布尔表达式。这允许你为 case 主体是否应该执行添加进一步的约束条件。当守卫子句计算结果为 false 时,执行将继续到下一个 case ,而不是退出整个 switch 语句。
错误处理
异常
你的 Dart 代码可以抛出和捕获异常。异常表示发生了某些意料之外的事情。如果异常没有被捕获,那么抛出异常的 Isolate 会被挂起,通常这个 Isolate 及其程序会被终止。
Isolate 说明:
Dart 中的 Isolate 是一种轻量级的线程模型,用于在 Dart 应用程序中实现并发执行和隔离。Dart 是单线程语言,支持异步,Isolate 是用于并发处理的一种机制,它允许在同一个进程内创建多个独立的执行上下文。
与Java相比,Dart中的所有异常都是未检查异常。方法不需要声明它们可能会抛出哪些异常,而且你也不需要捕获任何异常。
Dart 提供了 Exception 和 Error 类型,以及许多预定义的子类型。当然,你也可以定义自己的异常。然而,Dart 程序可以抛出任何非空对象作为异常,而不仅仅是 Exception 和 Error 对象。
throw
下面是一个抛出或引发异常的例子:
throw FormatException('预期至少有一个部分'); // 抛出一个格式异常
你也可以抛出任意对象:
throw 'Out of llamas!';
提示!
生产质量代码通常会抛出实现了 Error 或 Exception 类型的异常。
因为抛出异常是一个表达式,所以可以在 => 语句中抛出异常,也可以在任何允许表达式的地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();
catch
捕捉或捕获异常会阻止异常的传播(除非您重新抛出异常)。捕捉异常为您提供了处理异常的机会:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
为了处理可能抛出多种类型异常的代码,你可以指定多个 catch 子句。第一个与抛出的对象类型相匹配的 catch 子句将处理该异常。如果 catch 子句没有指定类型,则该子句可以处理任何类型的被抛出对象:
try {
breedMoreLlamas();
} on OutOfLlamasException { //当您需要 OutOfLlamasException 对象时使用 catch 获取,像下面一样
// 特定的异常
buyMoreLlamas();
} on Exception catch (e) {
// 任何其他的异常情况(Exception是异常的基类)
print('未知的异常: $e');
} catch (e) {
// 没有指定类型,处理所有类型
print('一些真正未知的东西: $e');
}
如前面的代码所示,您可以选择使用 on 或 catch ,或者同时使用它们。当您需要指定异常类型时,使用 on 。当您的异常处理程序需要异常对象时,使用 catch 。
你可以为 catch() 指定一个或两个参数。第一个参数是被抛出的异常,第二个参数是堆栈跟踪(一个 StackTrace 对象)。
try {
// ···
} on Exception catch (e) {
print('异常详细信息:\n $e');
} catch (e, s) {
print('异常详细信息:\n $e');
print('堆栈跟踪:\n $s');
}
若要部分处理异常,同时允许其传播,请使用 rethrow 关键字。
void misbehave() {
try {
dynamic foo = true;
print(foo++); // 运行时出错
} catch (e) {
print('misbehave() 部分处理 ${e.runtimeType}.');
rethrow; // 允许调用者看到异常。rethrow 将异常重新抛出给调用者,也可以使用 throw 抛出异常,但 throw 要指定异常类型,例如(throw Exception(e))。
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() 已完成处理 ${e.runtimeType}.');
}
}
finally
为了确保无论是否抛出异常,某些代码都能运行,请使用 finally 子句。如果没有任何 catch 子句与异常匹配,那么在 finally 子句执行完毕后,异常将继续传播:
try {
breedMoreLlamas(); //如果调用 breedMoreLlamas 函数出现异常,会保证先调用 finally 作用域的代码后再抛出异常
} finally {
// 即使抛出异常,也要始终进行清理
cleanLlamaStalls();
}
finally 子句在任何匹配的catch子句之后运行。
try {
breedMoreLlamas();
} catch (e) {
print('错误: $e'); // 首先处理异常
} finally {
cleanLlamaStalls(); // 然后进行清理
}
要了解更多信息,请查看核心库异常文档。
断言
在开发过程中,使用断言语句 — assert(<条件>, <可选信息>); — 如果布尔条件为假,则中断正常执行。
// 确保变量具有非空值
assert(text != null);
// 确保该值小于100
assert(number < 100);
// 确保这是一个https URL
assert(urlString.startsWith('https'));
为了给断言附加一条信息,可以将一个字符串作为第二个参数添加到assert函数中(后面可选地跟一个逗号):
assert(urlString.startsWith('https'),
'URL($urlString)应该以"https"开头');
assert 的第一个参数可以是任何解析为布尔值的表达式。如果表达式的值为 true,则断言成功,程序继续执行。如果为 false ,则断言失败,并抛出一个异常(即 AssertionError )。
断言到底在什么时候起作用?这取决于你正在使用的工具和框架:
- Flutter在调试模式下启用断言
- 仅用于开发的工具(如 webdev server )通常在默认情况下启用断言。
- 一些工具,如 dart run 和 dart compile js ,通过命令行标志:--enable-asserts 来支持断言
在生产代码中,断言会被忽略,并且不会计算 assert 的参数。