跳到内容

运算符

Crystal 支持许多运算符,它们可以有一个,两个或三个操作数。

运算符表达式实际上被解析为方法调用。例如 a + b 在语义上等效于 a.+(b),即在 a 上调用 + 方法,参数为 b

但是,关于运算符语法有一些特殊规则。

  • 通常放在接收者和方法名(即运算符)之间的点 (.) 可以省略。
  • 编译器会重构运算符调用链式序列,以实现运算符优先级。强制执行运算符优先级可以确保像 1 * 2 + 3 * 4 这样的表达式被解析为 (1 * 2) + (2 * 3),以遵守常规数学规则。
  • 常规方法名必须以字母或下划线开头,但运算符仅包含特殊字符。任何不以字母或下划线开头的 方法都是运算符方法。
  • 可用的运算符在编译器中是白名单(参见下面的运算符列表),它允许使用仅含符号的方法名并将其视为运算符,包括它们的优先级规则。

运算符的实现方式与任何常规方法一样,标准库提供了许多实现,例如用于数学表达式的实现。

定义运算符方法

大多数运算符可以作为常规方法实现。

可以为运算符赋予任何含义,但建议保持与通用运算符含义相似的语义,以避免难以理解和行为意外的模糊代码。

一些运算符是由编译器直接定义的,用户代码中无法重新定义。例如,反转运算符 !、赋值运算符 =复合赋值运算符(例如 ||=)和范围运算符。是否可以重新定义方法在下面的运算符表中的“可重载”列中指出。

一元运算符

一元运算符用前缀表示法编写,只有一个操作数。因此,方法实现不接受任何参数,只对 self 进行操作。

以下示例演示了 Vector2 类型作为二维向量,具有用于向量反转的一元运算符方法 -

struct Vector2
  getter x, y

  def initialize(@x : Int32, @y : Int32)
  end

  # Unary operator. Returns the inverted vector to `self`.
  def - : self
    Vector2.new(-x, -y)
  end
end

v1 = Vector2.new(1, 2)
-v1 # => Vector2(@x=-1, @y=-2)

二元运算符

二元运算符有两个操作数。因此,方法实现正好接收一个参数,表示第二个操作数。第一个操作数是接收者 self

以下示例演示了 Vector2 类型作为二维向量,具有用于向量相加的二元运算符方法 +

struct Vector2
  getter x, y

  def initialize(@x : Int32, @y : Int32)
  end

  # Binary operator. Returns *other* added to `self`.
  def +(other : self) : self
    Vector2.new(x + other.x, y + other.y)
  end
end

v1 = Vector2.new(1, 2)
v2 = Vector2.new(3, 4)
v1 + v2 # => Vector2(@x=4, @y=6)

按照惯例,二元运算符的返回类型应该是第一个操作数(接收者)的类型,因此 typeof(a <op> b) == typeof(a)。否则,赋值运算符 (a <op>= b) 会意外地改变 a 的类型。当然也有一些合理的例外情况。例如,在标准库中,整数类型的浮点除法运算符 / 始终返回 Float64,因为商不能限制在整数的值范围内。

三元运算符

条件运算符 (? :) 是唯一的三元运算符。它不解析为方法,其含义不能更改。编译器将其转换为 if 表达式。

运算符优先级

此列表按优先级排序,因此上面的条目比下面的条目绑定得更紧密。

类别 运算符
索引访问器 [], []?
一元运算符 +, &+, -, &-, !, ~
指数运算符 **, &**
乘法运算符 *, &*, /, //, %
加法运算符 +, &+, -, &-
移位运算符 <<, >>
二进制 AND &
二进制 OR/XOR |,^
相等运算符和子类型运算符 ==, !=, =~, !~, ===
比较运算符 <, <=, >, >=, <=>
逻辑 AND &&
逻辑 OR ||
范围 .., ...
条件运算符 ?:
赋值 =, []=, +=, &+=, -=, &-=, *=, &*=, /=, //=, %=, |=, &=,^=,**=,<<=,>>=, ||=, &&=
可变参数 *, **

运算符列表

算术运算符

一元运算符

运算符 描述 示例 可重载 结合性
+ 正号 +1 右结合
&+ 循环正号 &+1 右结合
- 负号 -1 右结合
&- 循环负号 &-1 右结合

乘法运算符

运算符 描述 示例 可重载 结合性
** 指数运算符 1 ** 2 右结合
&** 循环指数运算符 1 &** 2 右结合
* 乘法运算符 1 * 2 左结合
&* 循环乘法运算符 1 &* 2 左结合
/ 除法运算符 1 / 2 左结合
// 地板除法运算符 1 // 2 左结合
% 模运算符 1 % 2 左结合

加法运算符

运算符 描述 示例 可重载 结合性
+ 加法运算符 1 + 2 左结合
&+ 循环加法运算符 1 &+ 2 左结合
- 减法运算符 1 - 2 左结合
&- 循环减法运算符 1 &- 2 左结合

其他一元运算符

运算符 描述 示例 可重载 结合性
! 反转运算符 !true 右结合
~ 二进制补码 ~1 右结合

移位运算符

运算符 描述 示例 可重载 结合性
<< 左移运算符,追加 1 << 2, STDOUT << "foo" 左结合
>> 右移运算符 1 >> 2 左结合

二元运算符

运算符 描述 示例 可重载 结合性
& 二进制 AND 1 & 2 左结合
| 二进制 OR 1 | 2 左结合
^ 二进制 XOR 1 ^ 2 左结合

关系运算符

关系运算符测试两个值之间的关系。它们包括相等运算符不等运算符子类型运算符

相等运算符

相等运算符 == 检查操作数的值是否被认为相等。

不相等运算符 != 是表示反转的简写:a != b 应该等效于 !(a == b)

实现不相等运算符的类型必须确保遵守此规则。特殊实现对于性能方面很有用,因为不等性通常比相等性证明起来更快。

预期两个操作数都是可交换的,即 a == b 当且仅当 b == a。 编译器不会强制执行此规则,实现类型必须自行处理。

运算符 描述 示例 可重载 结合性
== 等于 1 == 2 左结合
!= 不等于 1 != 2 左结合

信息

标准库定义了 Reference#same? 作为另一个非运算符的相等性测试。 它检查引用标识,以确定两个值是否引用内存中的同一位置。

不等式

不等式运算符描述值之间的顺序。

三元比较运算符 <=>(也称为太空飞船运算符)表示由其返回值符号表示的两个元素之间的顺序。

运算符 描述 示例 可重载 结合性
< 小于 1 < 2 左结合
<= 小于或等于 1 <= 2 左结合
> 大于 1 > 2 左结合
>= 大于或等于 1 >= 2 左结合
<=> 三元比较 1 <=> 2 左结合

信息

标准库定义了 Comparable 模块,它从三元比较运算符派生所有其他不等式运算符以及等于运算符。

包含

模式匹配运算符 =~ 检查第一个操作数的值是否使用模式匹配与第二个操作数的值匹配。

无模式匹配运算符 !~ 表示反义。

case 包含运算符 ===(也称为case 相等运算符三等号)检查右侧操作数是否为左侧运算符描述的集合的成员。 确切的解释取决于所涉及的数据类型。

编译器在 case ... when 条件 中插入此运算符。

没有反向运算符。

运算符 描述 示例 可重载 结合性
=~ 模式匹配 "foo" =~ /fo/ 左结合
!~ 无模式匹配 "foo" !~ /fo/ 左结合
=== case 包含 /foo/ === "foo" 左结合

链接关系运算符

关系运算符 ==!====<><=>= 可以链接在一起,并被解释为一个复合表达式。 例如,a <= b <= c 被视为 a <= b && b <= c。 可以混合不同的运算符:a >= b <= c > d 等效于 a >= b && b <= c && c > d

建议仅组合具有相同 优先级类 的运算符,以避免出现意外的绑定行为。 例如,a == b <= c 等效于 a == b && b <= c,而 a <= b == c 等效于 a <= (b == c)

逻辑

运算符 描述 示例 可重载 结合性
&& 逻辑与 true && false 左结合
|| 逻辑或 true || false 左结合

范围

范围运算符用于 范围 字面量。

运算符 描述 示例 可重载
.. 包含范围 1..10
... 排除范围 1...10

散列运算符

散列运算符只能用于在方法参数中解构元组。 有关详细信息,请参阅 散列运算符和元组

运算符 描述 示例 可重载
* 散列运算符 *foo
** 双散列运算符 **foo

条件

编译器将 条件运算符 (? :) 内部重写为 if 表达式。

运算符 描述 示例 可重载 结合性
? : 条件 a == b ? c : d 右结合

赋值

赋值运算符 = 将第二个操作数的值分配给第一个操作数。 第一个操作数要么是变量(在这种情况下,运算符无法重新定义),要么是调用(在这种情况下,运算符可以重新定义)。 有关详细信息,请参阅 赋值

运算符 描述 示例 可重载 结合性
= 变量赋值 a = 1 右结合
= 调用赋值 a.b = 1 右结合
[]= 索引赋值 a[0] = 1 右结合

组合赋值

赋值运算符 = 是所有将运算符与赋值组合的运算符的基础。 一般形式为 a <op>= b,编译器将其转换为 a = a <op> b

一般扩展公式的例外是逻辑运算符

  • a ||= b 转换为 a || (a = b)
  • a &&= b 转换为 a && (a = b)

a 是索引访问器 ([]) 时,还有一个特殊情况,它被更改为可空变体(右侧的 []?

  • a[i] ||= b 转换为 a[i] = (a[i]? || b)
  • a[i] &&= b 转换为 a[i] = (a[i]? && b)

所有转换都假定接收器 (a) 是一个变量。 如果它是一个调用,则替换在语义上是等效的,但实现稍微复杂一些(引入一个匿名临时变量),并期望 a= 是可调用的。

接收器不能是除变量或调用以外的任何其他东西。

运算符 描述 示例 可重载 结合性
+= 加法赋值 i += 1 右结合
&+= 包装加法赋值 i &+= 1 右结合
-= 减法赋值 i -= 1 右结合
&-= 包装减法赋值 i &-= 1 右结合
*= 乘法赋值 i *= 1 右结合
&*= 包装乘法赋值 i &*= 1 右结合
/= 除法赋值 i /= 1 右结合
//= 地板除法赋值 i //= 1 右结合
%= 取模赋值 i %= 1 右结合
|= 二进制或赋值 i |= 1 右结合
&= 二进制与赋值 i &= 1 右结合
^= 二进制异或赋值 i ^= 1 右结合
**= 指数赋值 i **= 1 右结合
<<= 左移赋值 i <<= 1 右结合
>>= 右移赋值 i >>= 1 右结合
||= 逻辑或赋值 i ||= true 右结合
&&= 逻辑与赋值 i &&= true 右结合

索引访问器

索引访问器用于按索引或键查询值,例如数组项或映射条目。 可空变体 []? 在索引未找到时应该返回 nil,而不可空变体在这种情况下应该抛出异常。 标准库中的实现通常会抛出 KeyErrorIndexError

运算符 描述 示例 可重载
[] 索引访问器 ary[i]
[]? 可空索引访问器 ary[i]?