Skip to Content

运算符(Operators)

表达式靠运算符连接。本页按族过一遍全部运算符,优先级规则集中在最后。

算术

print(7 / 2, -7 / 2, 7 % 2, -7 % 2); print(2 + 3 * 4); print(2 ** 10, 2 ** 3 ** 2, -2 ** 2);
3 -3 1 -1 14 1024 512 -4
  • 整数除法向零截断:-7 / 2 得 -3,不是 -4,取模的符号跟着被除数走,-7 % 2 得 -1。
  • ** 是幂,右结合:2 ** 3 ** 2 按 2 ** (3 ** 2) 算出 512。
  • 一元负号的操作数包含整个幂:-2 ** 2 是 -(2 ** 2),得 -4。想要 (-2) ** 2 就写括号。

整数溢出:定宽回绕

整数算术的默认契约是 unchecked 定宽回绕:结果超出该宽度就从另一端绕回来,不会报错。int32 的最大值加 1 绕回最小值,uint32 的最大值加 1 绕回 0:

var m: int32 = 2147483647; print(m + 1); var u: uint32 = 4294967295; print(u + 1);
-2147483648 0

int64 同律,结果类型不变。要显式检查溢出,需要使用 Math 的 Checked 族:CheckedAdd / CheckedSub / CheckedMul,溢出时得 null 而不是绕回:

print(Math.CheckedAdd(9223372036854775807, 1) == null); print(Math.CheckedMul(3037000500, 3037000500) == null);
True True

各宽度的字面量规则与 Checked 族的常量域用法见整数。

字符串上的 + 与 *

+ 只要有一侧是字符串就做拼接,另一侧的值自动字符串化,两个方向都合法。* 一侧是字符串、一侧是整数时做重复:

print("n=" + 5, 5 + "=n", "ab" * 3);
n=5 5=n ababab

拼接不带分隔符,需要空格或标点自己写进字面量。

比较与相等

< <= > >= == != 六个比较符都返回 bool。字符串的排序比较按序数(Ordinal)逐字符比码点:

print("Z" < "a", "ab" == "ab");
True True

大写 Z 的码点小于小写 a,所以 "Z" < "a" 成立。

逻辑与位

&&、||、! 的操作数必须是 bool,整数直接放上去报错,位测试写成显式比较。位运算 & | ^ ~ << >> 作用于整数:

print(true && false, true || false, !true); print(5 & 3, 5 | 3, 5 ^ 3, ~5, 1 << 4);
False True False 1 7 6 -6 16

三元

cond ? a : b 是表达式,右结合,可以嵌套:

val x = 5; print(x > 0 ? x > 3 ? "大" : "小" : "非正");
大

嵌套读法是 x > 0 ? (x > 3 ? "大" : "小") : "非正"。需要返回值的分支选择如果超过两层,改用 match 更清楚。

范围与 in

a..b 是半开区间,a..=b 是闭区间,边界必须是整数。in 判断成员归属:

print(5 in 1..10, 10 in 1..10, 10 in 1..=10); val r = 1..5; print(3 in r, r);
True False True True 1..5

范围是值,可以装进变量再判。in 也接列表、字符串(子串语义)等可迭代类型。范围运算符不结合,1..2..3 直接报 MS2014。带步进的遍历写 for i in 0..10 step 3,见循环。

管道

管道运算符有四种形态:a |> f 等价 f(a),把左侧值作为实参传给右侧函数。带实参形 a |> f(x) 等价 f(a, x)。判空管道 a ?|> f 左侧为 null 时短路得 null。容错管道 a |>? f 等价 try? f(a)。传播管道 a |>! f 等价 try! f(a):

func Dbl(n: int) -> int => n * 2; func Add(a: int, b: int) -> int => a + b; print(21 |> Dbl, 21 |> Add(10));
42 31

目标表达式里的 _ 是占位符,左侧值落在 _ 标的位置(xs |> _[0]、", ".Join(_) 都成立)。链式管道从左往右流,适合把一串变换排成阅读顺序。四种运算符与占位符的完整口径见管道。

空条件运算符

可空类型(T?)有一组专用运算符:

写法名字行为
o?.m空条件成员o 为 null 时整个表达式为 null
o?[i]空条件索引同上,下标形式
a ?? b空合并a 非 null 取 a,否则取 b
a ??= b空合并赋值a 为 null 时才赋值
o!非空断言断言非 null,运行期为 null 时报 MS6045
var s: string? = null; print(s ?? "默认", s?.Length ?? -1); s = "内容"; print(s); var nickname: string? = null; nickname ??= "无名氏"; print(nickname);
默认 -1 内容 无名氏

可空值参与算术时结果也是可空:任一侧为 null,整个表达式为 null。

val a: int? = 2; val b: int? = null; print(a + 1, a + b ?? -1);
3 -1

a + b 是 null,?? -1 补上默认值。注意 ?? 的优先级低于全部算术,a + b ?? -1 按 (a + b) ?? -1 分组。

可空类型的更多系统讲解见可空类型。

with 的优先级

with 是高优先级后缀,挂在它左侧最近的表达式上,不吃掉整个算式:

print(vec2(1, 0) + vec2(2, 3) with { x: 9.0 });
vec2(10.0, 3.0)

实际算的是 1,0 + (2,3 并更新 x 为 9),得 (10, 3)。想对整个和做更新要加括号。with 的完整用法见向量与颜色。

自定义运算符

类与结构体可以重载运算符:声明用裸 operator 形式,首参必须是接收者 self。可重载的运算符全集:+ - * / % == != < <= > >= & | ^ ~:

class Vec { public var x: int = 0; public var y: int = 0; public operator +(self, other: Vec) -> Vec { return new Vec() { x: self.x + other.x, y: self.y + other.y }; } } func main() -> int { val a = new Vec() { x: 1, y: 2 }; val b = new Vec() { x: 3, y: 4 }; val s = a + b; print(s.x, s.y); return 0; }
4 6

运算符重载同时是协议运算符签名的满足面:类型 impl 一个含 operator 签名的协议时,需逐条提供同形重载。重载 == / != 会接管该类型的判等口径。未重载时的全语言二分口径见判等与哈希。转换运算符 conversion 是另一套声明,见类型转换。

注意

  • 赋值与复合赋值(= += -= *= /= %= **= &= |= ^= <<= >>= ??=)右结合;&&= 与 ||= 不存在。
  • 后缀 i++ / i-- 是语句糖,等价 i += 1。
  • 完整优先级从低到高:赋值 → 三元 → 管道 → || → && → ?? → 相等与 in → is as → 关系 → 位或 → 位异或 → 位与 → 移位 → 范围 → 加减 → 乘除模 → 幂 → 一元 → 空条件 → with → 成员与调用。
最后更新于 2026年10月9日