运算符(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
0int64 同律,结果类型不变。要显式检查溢出,需要使用 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 -1a + 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→isas→ 关系 → 位或 → 位异或 → 位与 → 移位 → 范围 → 加减 → 乘除模 → 幂 → 一元 → 空条件 →with→ 成员与调用。
