运算符¶
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]? |
是 |