运算符¶
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,而不可空变体在这种情况下应该抛出异常。 标准库中的实现通常会抛出 KeyError 或 IndexError。
| 运算符 | 描述 | 示例 | 可重载 |
|---|---|---|---|
[] |
索引访问器 | ary[i] |
是 |
[]? |
可空索引访问器 | ary[i]? |
是 |