運算子¶
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 | || |
範圍 | .. , ... |
條件 | ?: |
賦值 | = , []= , += , &+= , -= , &-= , *= , &*= , /= , //= , %= , |= , &= ,^= ,**= ,<<= ,>>= , ||= , &&= |
Splat | * , ** |
運算子列表¶
算術運算子¶
一元¶
運算子 | 描述 | 範例 | 可多載 | 結合性 |
---|---|---|---|---|
+ |
正數 | +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)
。
實作不等號運算子的類型必須確保遵守此規則。為了效能考量,特殊實作可能很有用,因為不等式通常可以比等式更快地被證明。
這兩個運算子都應該是可交換的,即當且僅當 b == a
時 a == b
。編譯器不會強制執行這一點,實作類型必須自行處理。
運算子 | 描述 | 範例 | 可多載 | 結合性 |
---|---|---|---|---|
== |
等於 | 1 == 2 |
是 | 左 |
!= |
不等於 | 1 != 2 |
是 | 左 |
資訊
標準函式庫定義了 Reference#same?
作為另一個非運算子的相等性測試。它檢查參考同一性,判斷兩個值是否參考記憶體中的相同位置。
不等式¶
不等式運算子描述數值之間的順序。
三向比較運算子 <=>
(也稱為太空船運算子) 以其回傳值的符號表達兩個元素之間的順序。
運算子 | 描述 | 範例 | 可多載 | 結合性 |
---|---|---|---|---|
< |
小於 | 1 < 2 |
是 | 左 |
<= |
小於或等於 | 1 <= 2 |
是 | 左 |
> |
大於 | 1 > 2 |
是 | 左 |
>= |
大於或等於 | 1 >= 2 |
是 | 左 |
<=> |
三向比較 | 1 <=> 2 |
是 | 左 |
資訊
標準函式庫定義了 Comparable
模組,它從三向比較運算子衍生出所有其他不等式運算子以及等號運算子。
涵蓋¶
模式匹配運算子 =~
檢查第一個運算元的值是否與第二個運算元的值符合模式匹配。
無模式匹配運算子 !~
表示反向。
案例涵蓋運算子 ===
(也稱為案例等號運算子或三等號,但不精確) 檢查右側運算元是否為左側運算元所描述的集合的成員。確切的解釋會根據所涉及的資料類型而有所不同。
編譯器在 case ... when
條件 中插入此運算子。
沒有反向運算子。
運算子 | 描述 | 範例 | 可多載 | 結合性 |
---|---|---|---|---|
=~ |
模式匹配 |
|
是 | 左 |
!~ |
無模式匹配 |
|
是 | 左 |
=== |
案例涵蓋 |
|
是 | 左 |
串聯關係運算子¶
關係運算子 ==
、!=
、===
、<
、>
、<=
和 >=
可以串聯在一起,並被解釋為複合表達式。例如,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)
。
邏輯¶
運算子 | 描述 | 範例 | 可多載 | 結合性 |
---|---|---|---|---|
&& |
邏輯 AND |
|
否 | 左 |
|| |
邏輯 OR |
|
否 | 左 |
範圍¶
範圍運算子用於 範圍 字面值。
運算子 | 描述 | 範例 | 可多載 |
---|---|---|---|
.. |
包含範圍 | 1..10 |
否 |
... |
排除範圍 | 1...10 |
否 |
展開¶
展開運算子只能用於在方法參數中解構元組。有關詳細資訊,請參閱 展開和元組。
運算子 | 描述 | 範例 | 可多載 |
---|---|---|---|
* |
展開 |
|
否 |
** |
雙重展開 |
|
否 |
條件¶
編譯器會將 條件運算子 (? :
) 在內部重寫為 if
表達式。
運算子 | 描述 | 範例 | 可多載 | 結合性 |
---|---|---|---|---|
? : |
條件 |
|
否 | 右 |
賦值¶
賦值運算子 =
將第二個運算元的值賦予第一個運算元。第一個運算元要么是一個變數 (在這種情況下,運算子不能被重新定義),要么是一個呼叫 (在這種情況下,運算子可以被重新定義)。有關詳細資訊,請參閱 賦值。
運算子 | 描述 | 範例 | 可多載 | 結合性 |
---|---|---|---|---|
= |
變數賦值 |
|
否 | 右 |
= |
呼叫賦值 |
|
是 | 右 |
[]= |
索引賦值 |
|
是 | 右 |
複合賦值¶
賦值運算子 =
是所有將運算子與賦值組合的運算子的基礎。一般形式是 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=
是可呼叫的。
接收者不能是變數或呼叫以外的任何東西。
運算子 | 描述 | 範例 | 可多載 | 結合性 |
---|---|---|---|---|
+= |
加法和賦值 |
|
否 | 右 |
&+= |
環繞加法和賦值 |
|
否 | 右 |
-= |
減法和賦值 |
|
否 | 右 |
&-= |
環繞減法和賦值 |
|
否 | 右 |
*= |
乘法和賦值 |
|
否 | 右 |
&*= |
環繞乘法和賦值 |
|
否 | 右 |
/= |
除法和賦值 |
|
否 | 右 |
//= |
地板除法和賦值 |
|
否 | 右 |
%= |
取模和賦值 |
|
是 | 右 |
|= |
二進制或和賦值 |
|
否 | 右 |
&= |
二進制與和賦值 |
|
否 | 右 |
^= |
二進制互斥或和賦值 |
|
否 | 右 |
**= |
指數和賦值 |
|
否 | 右 |
<<= |
左移和賦值 |
|
否 | 右 |
>>= |
右移和賦值 |
|
否 | 右 |
||= |
邏輯或和賦值 |
|
否 | 右 |
&&= |
邏輯與和賦值 |
|
否 | 右 |
索引存取器¶
索引存取器用於依索引或鍵查詢值,例如陣列項目或映射條目。當找不到索引時,可為空變體 []?
應該回傳 nil
,而不可為空變體在這種情況下會引發錯誤。標準函式庫中的實作通常會引發 KeyError
或 IndexError
。
運算子 | 描述 | 範例 | 可多載 |
---|---|---|---|
[] |
索引存取器 |
|
是 |
[]? |
可為空索引存取器 |
|
是 |