运算符
Dart 支持下表的操作符。你可以将这些运算符实现为 一个类的成员。
描述 | 运算符 | ||
---|---|---|---|
一元后缀 | 表达式++ 表达式-- () [] . ?. | ||
一元前缀 | -表达式 !表达式 ~表达式 ++表达式 --表达式 | ||
乘除法 | * / % ~/ | ||
加减法 | + - | ||
位运算 | << >> >>> | ||
二进制与 | & | ||
二进制异或 | ^ | ||
二进制或 | ` | ` | |
关系和类型测试 | >= > <= < as is is! | ||
相等判断 | == != | ||
逻辑与 | && | ||
逻辑或 | ` | ` | |
空判断 | ?? | ||
条件表达式 | 表达式 1 ? 表达式 2 : 表达式 3 | ||
级联 | .. ?..``.. ?.. | ||
赋值 | = *= /= += -= &= ^= 等等…… |
上述运算符优先级是对 Dart 解析器行为的效仿。更准确的描述,请参阅 Dart 语言规范 中的语法。
一旦你使用了运算符,就创建了表达式。下面是一些运算符表达式的示例:
a++
a + b
a = b
a == b
c ? a : b
a is T
在 运算符表 中,运算符的优先级按先后排列,即第一行优先级最高,最后一行优先级最低,而同一行中,最左边的优先级最高,最右边的优先级最低。例如:%
运算符优先级高于 ==
,而 ==
高于 &&
。根据优先级规则,那么意味着以下两行代码执行的效果相同:
// 括号改善可读性
if ((n % i == 0) && (d % i == 0)) ...
// 同上
if (n % i == 0 && d % i == 0) ..
算术运算符
Dart 支持常用的算术运算符:
运算符 | 描述 |
---|---|
+ | 加 |
– | 减 |
-表达式 | 一元负, 也可以作为反转(反转表达式的符号) |
* | 乘 |
/ | 除 |
~/ | 除并取整 |
% | 取模 |
示例:
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder
assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
Dart 还支持自增自减操作。
Operator++var | var = var + 1 (表达式的值为 var + 1 ) |
---|---|
var++ | var = var + 1 (表达式的值为 var ) |
--var | var = var – 1 (表达式的值为 var – 1 ) |
var-- | var = var – 1 (表达式的值为 var ) |
示例:
int a;
int b;
a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1
a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0
a = 0;
b = --a; // Decrement a before b gets its value.
assert(a == b); // -1 == -1
a = 0;
b = a--; // Decrement a AFTER b gets its value.
assert(a != b); // -1 != 0
关系运算符
下表列出了关系运算符及含义:
Operator== | 相等 |
---|---|
!= | 不等 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
要判断两个对象 x 和 y 是否表示相同的事物使用 ==
即可。(在极少数情况下,可能需要使用 identical() 函数来确定两个对象是否完全相同。)。下面是 ==
运算符的一些规则:
- 假设有变量 x 和 y,且 x 和 y 至少有一个为 null,则当且仅当 x 和 y 均为 null 时 x == y 才会返回 true,否则只有一个为 null 则返回 false。
x.==(y)
将会返回值,这里不管有没有 y,即 y 是可选的。也就是说==
其实是 x 中的一个方法,并且可以被重写。详情请查阅重写运算符。
下面的代码给出了每一种关系运算符的示例:
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);
重写运算符示例:
class Person {
final String firstName, lastName;
Person(this.firstName, this.lastName);
// 重写 hashCode,实现策略源于 Effective Java,
// 第11章。
@override
int get hashCode {
int result = 17;
result = 37 * result + firstName.hashCode;
result = 37 * result + lastName.hashCode;
return result;
}
// 如果重写了 hashCode,通常应该从新实现 == 操作符。
@override
bool operator ==(dynamic other) {
if (other is! Person) return false;
Person person = other;
return (person.firstName == firstName &&
person.lastName == lastName);
}
}
void main() {
var p1 = Person('Bob', 'Smith');
var p2 = Person('Bob', 'Smith');
var p3 = 'not a person';
assert(p1.hashCode == p2.hashCode);
assert(p1 == p2);
assert(p1 != p3);
}
类型判断运算符
as
、is
、is!
运算符是在运行时判断对象类型的运算符。
Operator | Meaning |
---|---|
as | 类型转换(也用作指定 类前缀)) |
is | 如果对象是指定类型则返回 true |
is! | 如果对象是指定类型则返回 false |
当且仅当 obj
实现了 T
的接口,obj is T
才是 true。例如 obj is Object
总为 true,因为所有类都是 Object 的子类。
仅当你确定这个对象是该类型的时候,你才可以使用 as
操作符可以把对象转换为特定的类型。例如:
(employee as Person).firstName = 'Bob';
如果你不确定这个对象类型是不是 T
,请在转型前使用 is T
检查类型。
if (employee is Person) {
// Type check
employee.firstName = 'Bob';
}
上述两种方式是有区别的:如果
employee
为 null 或者不为Person
类型,则第一种方式将会抛出异常,而第二种不会。
赋值运算符
可以使用 =
来赋值,同时也可以使用 ??=
来为值为 null 的变量赋值。
// Assign value to a
a = value;
// Assign value to b if b is null; otherwise, b stays the same
b ??= value;
像 +=
这样的赋值运算符将算数运算符和赋值运算符组合在了一起。
= | –= | /= | %= | >>= | ^= | |
---|---|---|---|---|---|---|
+= | *= | ~/= | <<= | &= | ` | =` |
下表解释了符合运算符的原理:
场景 | 复合运算 | 等效表达式 |
---|---|---|
假设有运算符 op: | a op= b | a = a op b |
示例: | a += b | a = a + b |
下面的例子展示了如何使用赋值以及复合赋值运算符:
var a = 2; // Assign using =
a *= 3; // Assign and multiply: a = a * 3
assert(a == 6);
逻辑运算符
使用逻辑运算符你可以反转或组合布尔表达式。
运算符 | 描述 | ||
---|---|---|---|
!表达式 | 对表达式结果取反(即将 true 变为 false,false 变为 true) | ||
` | ` | 逻辑或 | |
&& | 逻辑与 |
下面是使用逻辑表达式的示例:
if (!done && (col == 0 || col == 3)) {
// ...Do something...
}
按位和移位运算符
在 Dart 中,二进制位运算符可以操作二进制的某一位,但仅适用于整数。
运算符 | 描述 | |
---|---|---|
& | 按位与 | |
` | ` | 按位或 |
^ | 按位异或 | |
~表达式 | 按位取反(即将 “0” 变为 “1”,“1” 变为 “0”) | |
<< | 位左移 | |
>> | 位右移 |
下面是使用按位和移位运算符的示例:
final value = 0x22;
final bitmask = 0x0f;
assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
条件表达式
Dart 有两个特殊的运算符可以用来替代 if-else 语句:
条件 ? 表达式 1 : 表达式 2
如果条件为 true,执行表达式 1并返回执行结果,否则执行表达式 2 并返回执行结果。
表达式 1 ?? 表达式 2
如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值。
根据布尔表达式确定赋值时,请考虑使用 ?
和 :
var visibility = isPublic ? 'public' : 'private';
如果赋值是根据判定是否为 null 则考虑使用 ??
。
String playerName(String? name) => name ?? 'Guest';
上述示例还可以写成至少下面两种不同的形式,只是不够简洁:
// Slightly longer version uses ?: operator.
String playerName(String? name) => name != null ? name : 'Guest';
// Very long version uses if-else statement.
String playerName(String? name) {
if (name != null) {
return name;
} else {
return 'Guest';
}
}
级联运算符
级联运算符 (..
, ?..
) 可以让你在同一个对象上连续调用多个对象的变量或方法。
除了调用函数, 还可以访问同一对象上的字段属性。 这通常可以节省创建临时变量的步骤, 同时编写出更流畅的代码。 如果级联操作的对象可以为空,则对第一个操作使用?..
querySelector('#confirm') // 获取对象。
?..text = 'Confirm' // 调用成员变量。
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
第一句调用函数 querySelector()
, 返回获取到的对象。 获取的对象依次执行级联运算符后面的代码, 代码执行后的返回值会被忽略。
上面的代码等价于:
var button = querySelector('#confirm');
button?.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
级联运算符可以嵌套,例如:
final addressBook = (AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
在返回对象的函数中谨慎使用级联操作符。 例如,下面的代码是错误的:
var sb = StringBuffer();
sb.write('foo')
..write('bar'); // Error: 'void' 没有定义 'write' 函数。
sb.write()
函数调用返回 void, 不能在 void
对象上创建级联操作。
?..
运行在 2.12 和以上的 版本 中可用。
严格来说..
级联操作并非一个运算符而是 Dart 的特殊语法。
其他运算符
大多数其它的运算符,已经在其它的示例中使用过:
运算符 | 名字 | 描述 |
---|---|---|
() | 使用方法 | 代表调用一个方法 |
[] | 访问 List | 访问 List 中特定位置的元素 |
. | 访问成员 | 成员访问符 |
?. | 条件访问成员 | 与上述成员访问符类似,但是左边的操作对象不能为 null,例如 foo?.bar,如果 foo 为 null 则返回 null ,否则返回 bar |
更多关于 .
, ?.
和 ..
运算符介绍,请参考类.
流程控制语句
你可以使用下面的语句来控制 Dart 代码的执行流程:
if
和else
for
循环while
和do
-while
循环break
和continue
switch
和case
assert
使用 try-catch
和 throw
也能影响控制流,详情参考异常部分。
If 和 Else
Dart 支持 if - else
语句,其中 else
是可选的,比如下面的例子。你也可以参考条件表达式。
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
不同于 JavaScript,Dart 的 if 语句中的条件必须是布尔值而不能为其它类型。详情请查阅布尔值。
For 循环
你可以使用标准的 for
循环进行迭代。例如:
///StringBuffer的优势:
///String每更新一次就会new一个新的对象出来,更新次数上去之后,内存开销太大。
///而StringBuffer类型在更新(.append等操作)的过程中始终只有一个对象,开销大大减小。
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
在 Dart 语言中,for
循环中的闭包会自动捕获循环的 索引值 以避免 JavaScript 中一些常见的陷阱。假设有如下代码:
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
上述代码执行后会输出 0
和 1
,但是如果在 JavaScript 中执行同样的代码则会输出两个 2
。
如果要遍历的对象是一个可迭代对象(例如 List 或 Set),并且你不需要知道当前的遍历索引,则可以使用 for-in
方法进行 遍历:
for (var candidate in candidates) {
candidate.interview();
}
若想练习使用
for-in
,请参考 遍历集合 codelab。
可迭代对象同时可以使用 forEach() 方法作为另一种选择:
var collection = [1, 2, 3];
collection.forEach(print); // 1 2 3
While 和 Do-While
while
循环会在执行循环体前先判断条件:
while (!isDone()) {
doSomething();
}
do-while
循环则会 先执行一遍循环体 再判断条件:
do {
printLine();
} while (!atEndOfPage());
Break 和 Continue
使用 break
可以中断循环:
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
使用 continue
可以跳过本次循环直接进入下一次循环:
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
如果你正在使用诸如 List 或 Set 之类的 Iterable
对象,你可以用以下方式重写上述例子:
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());
Switch 和 Case
Switch 语句在 Dart 中使用 ==
来比较整数、字符串或编译时常量,比较的两个对象必须是同一个类型且不能是子类并且没有重写 ==
操作符。 枚举类型非常适合在 Switch
语句中使用。
Dart 中的 Switch 语句仅适用于有限的情况,比如使用解释器和扫描器的场景。
每一个非空的 case
子句都必须有一个 break
语句,也可以通过 continue
、throw
或者 return
来结束非空 case
语句。
不匹配任何 case
语句的情况下,会执行 default
子句中的代码:
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
下面的例子忽略了 case
子句的 break
语句,因此会产生错误:
var command = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();
// ERROR: Missing break
case 'CLOSED':
executeClosed();
break;
}
但是,Dart 支持空的 case
语句,允许其以 fall-through 的形式执行。
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
在非空 case
语句中想要实现 fall-through 的形式,可以使用 continue
语句配合 label 的方式实现:
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// Continues executing at the nowClosed label.
nowClosed:
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
每个 case
子句都可以有局部变量且仅在该 case 语句内可见。
断言
在开发过程中,可以在条件表达式为 false 时使用 — assert(条件, 可选信息)
; — 语句来打断代码的执行。你可以在本文中找到大量使用 assert 的例子。下面是相关示例:
// Make sure the variable has a non-null value.
assert(text != null);
// Make sure the value is less than 100.
assert(number < 100);
// Make sure this is an https URL.
assert(urlString.startsWith('https'));
assert
的第二个参数可以为其添加一个字符串消息。
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".');
assert
的第一个参数可以是值为布尔值的任何表达式。如果表达式的值为 true,则断言成功,继续执行。如果表达式的值为 false,则断言失败,抛出一个 AssertionError
异常。
如何判断 assert 是否生效?assert 是否生效依赖开发工具和使用的框架:
- Flutter 在调试模式时生效。
- 一些开发工具比如 dartdevc 通常情况下是默认生效的。
- 其他一些工具,比如
dart run
以及dart2js
通过在运行 Dart 程序时添加命令行参数--enable-asserts
使 assert 生效。
在生产环境代码中,断言会被忽略,与此同时传入 assert
的参数不被判断。
异常
Dart 代码可以抛出和捕获异常。异常表示一些未知的错误情况,如果异常没有捕获则会被抛出从而导致抛出异常的代码终止执行。
与 Java 不同的是,Dart 的所有异常都是非必检异常,方法不必声明会抛出哪些异常,并且你也不必捕获任何异常。
Dart 提供了 Exception
和 Error
两种类型的异常以及它们一系列的子类,你也可以定义自己的异常类型。但是在 Dart 中可以将任何非 null 对象作为异常抛出而不局限于 Exception 或 Error 类型。
抛出异常
下面是关于抛出或者 引发 异常的示例:
throw FormatException('Expected at least 1 section');
你也可以抛出任意的对象:
throw 'Out of llamas!';
优秀的代码通常会抛出
Error
或Exception
类型的异常。 因为抛出异常是一个表达式,所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();
捕获异常
捕获异常可以避免异常继续传递(重新抛出异常除外)。捕获一个异常可以给你处理它的机会:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
对于可以抛出多种异常类型的代码,也可以指定多个 catch 语句,每个语句分别对应一个异常类型,如果 catch 语句没有指定异常类型则表示可以捕获任意异常类型:
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}
如上述代码所示可以使用 on
或 catch
来捕获异常,使用 on
来指定异常类型,使用 catch
来捕获异常对象,两者可同时使用。
你可以为 catch
方法指定两个参数,第一个参数为抛出的异常对象,第二个参数为栈信息 StackTrace
对象:
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
关键字 rethrow
可以将捕获的异常再次抛出:
void misbehave() {
try {
dynamic foo = true;
print(foo++); // Runtime error
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}
Finally
无论是否抛出异常,finally
语句始终执行,如果没有指定 catch
语句来捕获异常,则异常会在执行完 finally
语句后抛出:
try {
breedMoreLlamas();
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
finally
语句会在任何匹配的 catch
语句后执行:
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // Handle the exception first.
} finally {
cleanLlamaStalls(); // Then clean up.
}
你可以阅读 Dart 核心库概览的 异常 章节获取更多相关信息。