Dart之运算符与表达式

一、前言
刚开始学 Dart 时,很多人会先看到一堆符号:+、-、*、/、=、==、&&、??、..。
这些符号看起来很零散,好像只能靠背。其实不用这样理解。
你可以先把运算符想象成代码里的“工具按钮”:有的负责计算,有的负责赋值,有的负责比较,有的负责判断条件,还有的专门处理空值。
表达式则是这些工具组合出来的一句话。比如 3 + 5 是一个表达式,age >= 18 也是一个表达式,name ?? '游客' 也是一个表达式。
这一篇我们就用系统化的方式,把 Dart 中常见的运算符和表达式讲清楚。
二、一句话理解
运算符是 Dart 用来完成计算、赋值、比较、判断等操作的符号;表达式是由变量、值、运算符和函数调用组合而成,并且能产生结果的一段代码。
简单说:
- 运算符:告诉代码“要做什么操作”。
- 表达式:把操作组合起来,得到一个结果。
- 学习重点:不要死背符号,要理解每类符号解决什么问题。
三、什么是运算符
运算符就是代码中的操作符号。
在生活中,我们会用不同工具处理不同事情:剪刀用来剪,胶水用来粘,计算器用来算数。代码里的运算符也是这样,不同符号负责不同操作。
先建立一张总表:
| 类型 | 常见符号 | 作用 | 常见结果 |
|---|---|---|---|
| 算术运算符 | +、-、*、/、~/、% |
做数学计算 | 数值 |
| 赋值运算符 | =、+=、-=、*=、/=、~/=、%= |
给变量赋值或更新变量 | 更新变量 |
| 比较运算符 | >、>=、<、<=、==、!= |
比较两个值 | bool |
| 逻辑运算符 | &&、` |
、!` |
|
| 自增自减 | ++、-- |
快速加 1 或减 1 | 更新变量 |
| 空感知运算符 | ??、??=、?. |
处理 null |
取决于表达式 |
| 级联运算符 | ..、?.. |
连续操作同一个对象 | 原对象 |
| 类型判断 | is、is! |
判断对象类型 | bool |
这张表不需要一次背下来,先知道每类符号负责什么事情,后面写代码时就能逐步熟悉。
3.1 算术运算符
算术运算符用于完成基础数学计算。
void main() {
print(3 + 5); // 8
print(3 - 5); // -2
print(3 * 5); // 15
print(3 / 5); // 0.6
print(3 ~/ 5); // 0
print(3 % 5); // 3
}
这里需要注意两个比较有 Dart 特点的符号:
/:普通除法,结果是double。~/:整除,只保留整数部分,结果是int。
% 是取余,它适合判断一个数能不能被另一个数整除。
void main() {
print(10 % 2); // 0,说明 10 可以被 2 整除
print(10 % 3); // 1,说明 10 除以 3 余 1
}
再看一个更贴近日常的例子:如果 10 个苹果平均分给 3 个人,每个人能拿几个,还剩几个?
void main() {
int apples = 10;
int people = 3;
print(apples ~/ people); // 3,每个人分到 3 个
print(apples % people); // 1,还剩 1 个
}
所以 ~/ 和 % 经常成对出现:
~/关心“完整分了几份”。%关心“分完还剩多少”。
3.2 赋值运算符
赋值运算符用于把右边的值交给左边的变量。
最常见的是 =。
void main() {
int count = 1;
print(count); // 1
}
如果变量需要在原来的基础上继续计算,可以使用复合赋值运算符。
void main() {
int count = 10;
count += 2; // 等价于 count = count + 2
print(count); // 12
count -= 3;
print(count); // 9
count *= 2;
print(count); // 18
count ~/= 4;
print(count); // 4
}
复合赋值的核心不是新语法多高级,而是让“基于原值更新”这件事写得更简洁。
常见复合赋值可以这样理解:
| 写法 | 等价写法 | 含义 |
|---|---|---|
a += b |
a = a + b |
在原值上增加 |
a -= b |
a = a - b |
在原值上减少 |
a *= b |
a = a * b |
在原值上相乘 |
a /= b |
a = a / b |
在原值上除以 |
a ~/= b |
a = a ~/ b |
在原值上整除 |
a %= b |
a = a % b |
在原值上取余 |
注意:/= 的结果通常是 double,所以左侧变量类型要能接住结果。
void main() {
double price = 10;
price /= 4;
print(price); // 2.5
}
3.3 比较运算符
比较运算符用于比较两个值之间的关系,结果一定是 bool 类型。
void main() {
int age = 18;
print(age > 16); // true
print(age >= 18); // true
print(age < 18); // false
print(age <= 20); // true
print(age == 18); // true
print(age != 18); // false
}
这里最容易混淆的是 = 和 ==。
=:赋值,把右边交给左边。==:比较,判断左右是否相等。
void main() {
int age = 18; // 赋值
print(age == 18); // 比较
}
一个是“放进去”,一个是“看一样不一样”。
3.4 逻辑运算符
逻辑运算符用于组合多个布尔条件。
void main() {
int age = 20;
bool hasTicket = true;
print(age >= 18 && hasTicket); // true
print(age < 18 || hasTicket); // true
print(!hasTicket); // false
}
三个常见逻辑运算符:
&&:并且,左右都为true才是true。||:或者,只要有一个为true就是true。!:取反,把true变false,把false变true。
可以用生活场景理解:
- 进电影院:成年人并且有票,才能进场。
- 找交通方式:坐地铁或者打车,有一个可行就能出发。
- 取反:不是会员,就走非会员逻辑。
逻辑运算符还有一个很重要的特点:短路。
&& 遇到左边为 false 时,右边不会再执行,因为整体已经不可能为 true。
bool checkTicket() {
print('检查门票');
return true;
}
void main() {
int age = 16;
print(age >= 18 && checkTicket());
// age >= 18 为 false,所以 checkTicket 不会执行
}
|| 遇到左边为 true 时,右边也不会再执行,因为整体已经确定为 true。
bool useBackupPlan() {
print('启动备用方案');
return true;
}
void main() {
bool primaryOk = true;
print(primaryOk || useBackupPlan());
// primaryOk 为 true,所以 useBackupPlan 不会执行
}
短路不是细节,它会影响函数是否被调用、日志是否打印、网络请求是否触发。写条件时要知道右侧表达式可能不会执行。
四、Dart 中比较特别的运算符
除了基础运算符,Dart 还有一些非常常用、也很有语言特色的运算符。
4.1 自增自减运算符
自增自减运算符用于让变量快速加 1 或减 1。
void main() {
int i = 5;
print(++i); // 6,先加 1,再使用
print(i); // 6
print(i++); // 6,先使用,再加 1
print(i); // 7
}
前置和后置的区别是:
++i:先变化,再使用。i++:先使用,再变化。
如果只是单独写一行,差别不明显。
i++;
++i;
但如果放在 print()、赋值或表达式里,就要注意执行顺序。
自减也是同样规则:
void main() {
int i = 5;
print(--i); // 4,先减 1,再使用
print(i--); // 4,先使用,再减 1
print(i); // 3
}
实际开发里,如果表达式变复杂,我更建议拆开写。
count++;
print(count);
这种写法比把 count++ 藏在复杂表达式里更容易读。
4.2 空感知运算符
空感知运算符用于处理 null。
Dart 是空安全语言,很多时候我们需要明确告诉代码:如果这个值为空,应该怎么办。
void main() {
String? name;
String displayName = name ?? '游客';
print(displayName); // 游客
}
?? 的意思是:如果左边不是 null,就用左边;如果左边是 null,就用右边。
再看 ??=:
void main() {
String? name;
name ??= '默认用户';
print(name); // 默认用户
}
??= 的意思是:如果变量当前是 null,才把右边的值赋给它。
还有 ?.:
void main() {
String? name;
print(name?.length); // null
}
如果 name 是 null,就不会继续调用 length,而是直接返回 null。
把三个空感知运算符放在一起看,会更清楚:
| 运算符 | 作用 | 是否会修改变量 |
|---|---|---|
?? |
左边为空时使用右边 | 不会 |
??= |
左边变量为空时才赋值 | 会 |
?. |
左边为空时不继续访问成员 | 不会 |
例如用户昵称为空时显示默认文案:
void main() {
String? nickName;
print(nickName ?? '未设置昵称');
}
如果希望变量本身也被默认值填上,才使用 ??=:
void main() {
String? nickName;
nickName ??= '未设置昵称';
print(nickName);
}
如果访问一个可能为空的对象属性,使用 ?. 可以避免空对象继续调用成员。
class User {
User(this.name);
final String name;
}
void main() {
User? user;
print(user?.name); // null
}
4.3 级联运算符
级联运算符 .. 用于对同一个对象连续操作。
class User {
String name = '';
int age = 0;
void sayHello() {
print('Hello, $name');
}
}
void main() {
User user = User()
..name = 'Tom'
..age = 18
..sayHello();
}
如果不用级联运算符,写法通常是:
void main() {
User user = User();
user.name = 'Tom';
user.age = 18;
user.sayHello();
}
级联运算符的作用不是改变逻辑,而是让“连续设置同一个对象”这件事更集中。
Dart 还有空安全级联 ?..。当对象可能为空时,可以避免继续执行后续级联操作。
class User {
String name = '';
void sayHello() {
print('Hello, $name');
}
}
void main() {
User? user;
user
?..name = 'Tom'
..sayHello();
}
这里 user 是 null,所以后面的设置和方法调用不会继续执行。
使用级联时要注意:级联操作的目标始终是最开始的那个对象,而不是每一步调用的返回值。
4.4 类型判断运算符
类型判断运算符用于判断一个对象是不是某种类型。
void main() {
Object value = 'hello';
print(value is String); // true
print(value is! int); // true
}
is:判断是不是某个类型。is!:判断不是不是某个类型。
这在处理 Object、dynamic 或复杂数据时很常见。
void printValue(Object value) {
if (value is String) {
print(value.length);
}
}
当 value is String 成立后,Dart 能更明确地知道它是字符串,就可以使用字符串相关能力。
这类能力也叫类型提升。简单说,就是 Dart 看到你已经判断过类型,就会在对应代码块里把变量当成更具体的类型使用。
void printLength(Object value) {
if (value is String) {
print(value.length);
} else {
print('不是字符串');
}
}
如果判断不是某个类型,可以使用 is!:
void main() {
Object value = 100;
if (value is! String) {
print('value 不是字符串');
}
}
当你从接口、JSON、动态数据里拿到不确定类型的值时,类型判断非常有用。
4.5 条件访问和非空断言
除了上面几类,Dart 空安全里还有一个很常见的符号:!。
这里的 ! 不是逻辑取反,而是非空断言。
void main() {
String? name = 'Tom';
print(name!.length);
}
name! 的意思是:我确定 name 这里一定不是 null。
但它有风险。如果实际运行时值是 null,程序会报错。
void main() {
String? name;
print(name!.length); // 运行时报错
}
所以初学阶段要记住:
- 能用
?.和??安全处理时,不要急着用!。 !适合你非常确定某个值不为空的场景。
五、什么是表达式
表达式是能产生结果的一段代码。
一个值可以是表达式,一个变量可以是表达式,多个值和运算符组合起来也可以是表达式。
5.1 简单表达式
简单表达式通常由一个值、一个变量或一个很短的运算组成。
void main() {
int age = 18;
print(age);
print(3 + 5);
print(age >= 18);
}
这里的 age、3 + 5、age >= 18 都是表达式,因为它们都能产生结果。
5.2 复杂表达式
复杂表达式是多个表达式继续组合。
void main() {
int age = 20;
bool hasTicket = true;
bool canEnter = age >= 18 && hasTicket;
print(canEnter); // true
}
age >= 18 && hasTicket 就是一个复杂表达式。
它里面包含:
age >= 18:比较表达式。hasTicket:布尔变量表达式。&&:逻辑运算符。
最终整体产生一个 bool 结果。
5.3 条件表达式
条件表达式也叫三目运算符。
void main() {
int score = 80;
String result = score >= 60 ? '及格' : '不及格';
print(result);
}
结构是:
条件 ? 条件成立时的结果 : 条件不成立时的结果
它适合处理比较短的二选一逻辑。
如果逻辑很长,建议使用 if else,可读性更好。
5.4 箭头函数表达式
箭头函数适合写只有一行返回结果的函数。
int add(int a, int b) => a + b;
void main() {
print(add(3, 5)); // 8
}
它等价于:
int add(int a, int b) {
return a + b;
}
箭头函数不是为了炫技,而是让简单逻辑更简洁。
如果函数内部需要多行处理,就不要硬写成箭头函数。
5.5 表达式和语句的区别
表达式会产生结果,语句更像是一条完整指令。
void main() {
int a = 3 + 5;
print(a);
}
这里:
3 + 5是表达式,因为它产生结果8。int a = 3 + 5;是语句,因为它完成了一次变量声明和赋值。print(a);也是语句,因为它执行了一次打印动作。
不用把概念想复杂:表达式偏“算出一个东西”,语句偏“执行一件事情”。
5.6 运算符优先级
表达式里如果出现多个运算符,就会涉及优先级。
void main() {
print(2 + 3 * 4); // 14
print((2 + 3) * 4); // 20
}
* 的优先级高于 +,所以第一行先算 3 * 4。
如果你不确定优先级,或者希望别人一眼看懂,就加括号。
bool result = (age >= 18) && (hasTicket || isVip);
括号不是多余,它是在帮读者降低理解成本。
六、常见误区
6.1 把赋值和比较写混
初学者最常见的问题之一,就是分不清 = 和 ==。
int age = 18; // 赋值
bool ok = age == 18; // 比较
记住:
- 一个等号是放值。
- 两个等号是比较。
6.2 看到 ?? 就以为一定会修改变量
?? 不会修改变量,它只会返回一个结果。
void main() {
String? name;
String result = name ?? '游客';
print(name); // null
print(result); // 游客
}
真正可能修改变量的是 ??=。
void main() {
String? name;
name ??= '游客';
print(name); // 游客
}
6.3 过度使用复杂表达式
表达式可以组合,但不代表越长越好。
bool canEnter = age >= 18 && hasTicket && !isBlocked && score > 60;
如果条件越来越多,可以拆成更清楚的变量。
bool isAdult = age >= 18;
bool canUseTicket = hasTicket && !isBlocked;
bool passedCheck = score > 60;
bool canEnter = isAdult && canUseTicket && passedCheck;
代码不是越短越好,而是越容易理解越好。
6.4 滥用非空断言
! 写起来很方便,但它不是消除空值问题,而是把风险推迟到运行时。
String? name;
print(name!.length); // 运行时风险
更稳的写法是先给默认值,或先判断。
String? name;
print(name?.length ?? 0);
6.5 忽略返回值类型
有些运算符看起来像数学里的符号,但返回值类型不一定和你想的一样。
void main() {
print(5 / 2); // 2.5,double
print(5 ~/ 2); // 2,int
}
写 Dart 时要关注表达式结果是什么类型。尤其是赋值、函数返回值和条件判断里,类型不对会直接影响后续代码。
七、总结
这一篇我们系统梳理了 Dart 中的运算符与表达式。
运算符可以理解为代码中的工具按钮:算术运算符负责计算,赋值运算符负责更新变量,比较运算符负责判断关系,逻辑运算符负责组合条件,自增自减负责快速加减,空感知运算符负责处理 null,级联运算符负责连续操作同一个对象,类型判断运算符负责判断对象类型。
表达式则是由值、变量、运算符和函数调用组合出来的一段代码,它的特点是能产生结果。
学习这一节时,最重要的不是把所有符号一次背完,而是建立分类:
- 这个符号解决什么问题?
- 它返回什么结果?
- 它会不会修改变量?
- 它适合放在什么场景里?
把这几个问题想清楚,运算符和表达式就不再是一堆零散符号,而会变成你组织 Dart 代码的基础工具。
最后用一张表收束一下:
| 知识点 | 最该记住的点 |
|---|---|
/ 和 ~/ |
/ 得到 double,~/ 得到整数结果 |
= 和 == |
= 是赋值,== 是比较 |
&& 和 ` |
|
?? 和 ??= |
?? 返回默认值,??= 才会修改变量 |
?. 和 ! |
?. 是安全访问,! 是非空断言 |
.. |
连续操作同一个对象 |
is 和 is! |
判断类型,并帮助 Dart 做类型提升 |
| 表达式 | 能产生结果的一段代码 |
浙公网安备 33010602011771号