Dart支持下表所示的运算符。该表按从高到低的顺序显示了Dart的运算符结合性和优先级,这些是对Dart运算符关系的近似描述。您可以将其中许多运算符实现为类成员。
| 描述 | 运算符 | 结合性 |
|---|---|---|
| 后置一元运算符 | expr++、expr--、()、[]、?[]、.、?.、! | 无 |
| 前置一元运算符 | -expr、!expr、~expr、++expr、--expr、await expr | 无 |
| 乘法运算符 | *、/、%、~/ | 左 |
| 加法运算符 | +、- | 左 |
| 移位运算符 | <<、>>、>>> | 左 |
| 按位与 | & | 左 |
| 按位异或 | ^ | 左 |
| 按位或 | ||
| 关系和类型测试 | >=、>、<=、<、as、is、is! | 无 |
| 相等性判断 | ==、!= | 无 |
| 逻辑与 | && | 左 |
| 逻辑或 | ||
| 空值判断 | ?? | 左 |
| 条件运算符 | expr1 ? expr2 : expr3 | 右 |
| 级联运算符 | ..、?.. | 左 |
| 赋值运算符 | =、*=、/=、+=、-=、&=、^=等 | 右 |
| 展开运算符(见注释) | ...、...? | 无 |
警告
上表仅应作为参考指南。运算符优先级和结合性的概念是对语言语法中实际规则的近似描述。您可以在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) {
// ...
}警告
对于需要两个操作数的运算符,最左侧的操作数决定使用哪个方法。例如,如果有Vector对象和Point对象,则aVector + aPoint使用Vector的加法(+)方法。
算术运算符
Dart支持常用的算术运算符,如下表所示:
| 运算符 | 含义 |
|---|---|
| + | 加法 |
| - | 减法 |
| -expr | 一元取反(也称为否定,反转表达式的符号) |
| * | 乘法 |
| / | 除法(结果为双精度数) |
| ~/ | 除法(返回整数结果) |
| % | 取整数除法的余数(模运算) |
示例:
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // 结果为double
assert(5 ~/ 2 == 2); // 结果为int
assert(5 % 2 == 1); // 余数
assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');Dart还支持前置和后置递增/递减运算符:
| 运算符 | 含义 |
|---|---|
| ++var | 变量自增1(表达式值为var+1) |
| var++ | 变量自增1(表达式值为var原值) |
| --var | 变量自减1(表达式值为var-1) |
| var-- | 变量自减1(表达式值为var原值) |
示例:
int a;
int b;
a = 0;
b = ++a; // a先自增,再赋值给b
assert(a == b); // 1 == 1
a = 0;
b = a++; // a先赋值给b,再自增
assert(a != b); // 1 != 0
a = 0;
b = --a; // a先自减,再赋值给b
assert(a == b); // -1 == -1
a = 0;
b = a--; // a先赋值给b,再自减
assert(a != b); // -1 != 0相等性和关系运算符
下表列出了相等性和关系运算符的含义:
| 运算符 | 含义 |
|---|---|
| == | 相等(见下文讨论) |
| != | 不相等 |
| > | 大于 |
| < | 小于 |
| >= | 大于或等于 |
| <= | 小于或等于 |
要测试两个对象x和y是否表示同一事物,使用==运算符。(在极少数需要判断两个对象是否为完全相同实例的情况下,应使用identical()函数。)==运算符的工作原理如下:
- 如果x或y为null,则当两者都为null时返回true,仅其中一个为null时返回false。
- 返回x调用==方法并传入参数y的结果。(没错,像==这样的运算符是在第一个操作数上调用的方法。详情见运算符部分。)
示例:
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);类型测试运算符
as、is和is!运算符可用于在运行时检查类型:
| 运算符 | 含义 |
|---|---|
| as | 类型转换(也用于指定库前缀) |
| is | 如果对象具有指定类型则为true |
| is! | 如果对象不具有指定类型则为true |
如果obj实现了T指定的接口,则obj is T的结果为true。例如,obj is Object?始终为true。
仅当确定对象属于特定类型时,才使用as运算符将其强制转换为该类型。示例:
(employee as Person).firstName = 'Bob';如果不确定对象是否为类型T,可先使用is T检查类型,再使用该对象:
if (employee is Person) {
// 类型检查通过
employee.firstName = 'Bob';
}注意
两段代码不等价。如果employee为null或不是Person类型,第一个示例会抛出异常,而第二个示例不执行任何操作。
赋值运算符
如前所述,可以使用=运算符赋值。若要仅在被赋值变量为null时赋值,可使用??=运算符:
// 直接赋值给a
a = value;
// 若b为null则赋值给b,否则保持不变
b ??= value;复合赋值运算符(如+=)将操作与赋值结合:
| 运算符 | 等价于 |
|---|---|
| a op= b | a = a op b |
| 示例:a += b | a = a + b |
示例:
var a = 2; // 使用=赋值
a *= 3; // 赋值并相乘:a = a * 3
assert(a == 6);逻辑运算符
可以使用逻辑运算符反转或组合布尔表达式:
| 运算符 | 含义 |
|---|---|
| !expr | 反转表达式(将false变为true,反之亦然) |
| && | 逻辑与 |
示例:
if (!done && (col == 0 || col == 3)) {
// ...执行某些操作...
}按位和移位运算符
在Dart中可以操作数字的各个二进制位,通常将这些按位和移位运算符与整数配合使用:
| 运算符 | 含义 |
|---|---|
| & | 按位与 |
| ^ | 按位异或 |
| ~expr | 一元按位取反(0变1,1变0) |
| << | 左移 |
| >> | 右移 |
| >>> | 无符号右移 |
注意
大整数或负操作数的按位运算行为可能因平台而异。更多信息请查看按位运算的平台差异。
示例:
final value = 0x22;
final bitmask = 0x0f;
assert((value & bitmask) == 0x02); // 按位与
assert((value & ~bitmask) == 0x20); // 按位与非
assert((value | bitmask) == 0x2f); // 按位或
assert((value ^ bitmask) == 0x2d); // 按位异或
assert((value << 4) == 0x220); // 左移
assert((value >> 4) == 0x02); // 右移
// 右移示例(在Web平台上因操作数被掩码为32位而行为不同)
assert((-value >> 4) == -0x03);
assert((value >>> 4) == 0x02); // 无符号右移
assert((-value >>> 4) > 0); // 无符号右移结果为正数版本说明
运算符(又称无符号右移或三目移位)要求语言版本至少为2.14。
条件表达式
Dart有两个运算符可简洁地计算原本需要if-else语句的表达式:
- 条件运算符:
condition ? expr1 : expr2- 如果condition为true,计算expr1(并返回其值);否则计算并返回expr2的值。
- 空值合并运算符:
expr1 ?? expr2- 如果expr1不为null,返回其值;否则计算并返回expr2的值。
当需要根据布尔表达式赋值时,可使用条件运算符?和::
var visibility = isPublic ? 'public' : 'private';如果布尔表达式检查null,可使用空值合并运算符??:
String playerName(String? name) => name ?? 'Guest';上述示例还可以用至少两种其他方式编写,但不够简洁:
// 使用?:运算符的较长版本
String playerName(String? name) => name != null ? name : 'Guest';
// 使用if-else语句的冗长版本
String playerName(String? name) {
if (name != null) {
return name;
} else {
return 'Guest';
}
}级联表示法
级联(..、?..)允许对同一对象执行一系列操作。除了访问实例成员,还可以调用该对象的实例方法。这通常省去创建临时变量的步骤,并使代码更流畅。
示例:
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;构造函数Paint()返回一个Paint对象,级联表示法后的代码操作该对象,忽略可能返回的任何值。
上述示例等效于:
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;如果级联操作的对象可能为null,则对第一个操作使用空值安全级联(?..)。以?..开头可确保不会对null对象执行任何级联操作:
document.querySelector('#confirm') // 获取对象
?..textContent = 'Confirm' // 使用其成员
..classList.add('important')
..onClick.listen((e) => window.alert('Confirmed!'))
..scrollIntoView();等效于:
final button = document.querySelector('#confirm');
button?.textContent = 'Confirm';
button?.classList.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));
button?.scrollIntoView();还可以嵌套级联:
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'); // 错误:void类型没有'write'方法sb.write()调用返回void,无法对void执行级联操作。
注意
严格来说,级联的“双点”表示法不是运算符,它只是Dart语法的一部分。
[...a + b] // 合法,a + b 需返回集合其他运算符
以下是其他常见运算符的说明:
| 运算符 | 名称 | 含义 |
|---|---|---|
() | 函数调用 | 执行函数调用,如 foo()、list.length(方法调用本质也是函数调用) |
[] | 下标访问 | 调用可重写的 [] 运算符,如 list[0] 等价于 list.[]!(0) |
?[] | 条件下标访问 | 左操作数可为 null,如 list?[0] 等价于 list == null ? null : list[0] |
. | 成员访问 | 访问对象属性或方法,如 obj.name、obj.method() |
?. | 条件成员访问 | 左操作数可为 null,如 obj?.name 等价于 obj == null ? null : obj.name |
! | 非空断言 | 将表达式强制转换为非空类型,若为 null 抛出运行时异常,如 obj!.name |
运算符重载
Dart 允许通过实现类成员方法来重载部分运算符,常见可重载的运算符及其对应方法如下:
| 运算符 | 对应方法 | 示例(在类中定义) |
|---|---|---|
+ | operator +(other) | Vector operator +(Vector other) => Vector(x + other.x, y + other.y); |
- | operator -() | Vector operator -() => Vector(-x, -y);(单目取反) |
[] | operator [](index) | int operator [](int index) => _items[index]; |
[]= | operator []=(index, value) | void operator []=(int index, int value) => _items[index] = value; |
== | operator ==(other) | bool operator ==(Object other) => other is Vector && x == other.x && y == other.y; |
> | operator >(other) | bool operator >(Vector other) => x * x + y * y > other.x * other.x + other.y * other.y; |
注意:
- 运算符重载仅适用于自定义类,内置类型(如
int、String)的运算符行为不可改变。 ==运算符重载时,建议同时重写hashCode方法以保证哈希一致性。
总结
Dart 的运算符系统设计灵活,既保留了传统编程语言的习惯,又通过级联、展开、空安全等特性增强了表达能力。在实际开发中,需注意:
- 利用空安全运算符(
?.、??、!)避免null相关错误。 - 通过括号明确优先级,提高代码可读性(如
(a + b) * c优于a + b * c)。 - 合理使用级联符号简化对象配置代码,但避免在
void返回值上使用。 - 自定义类时,通过运算符重载提升接口友好性(如向量运算、数据结构操作)。
更多细节请参考 Dart 语言规范中的运算符章节 和 Effective Dart 中的运算符使用指南。