跳到內容

運算子

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 << 2STDOUT << "foo"
>> 右移 1 >> 2

二元

運算子 描述 範例 可多載 結合性
& 二元 AND 1 & 2
| 二元 OR 1 | 2
^ 二元 XOR 1 ^ 2

關係運算子

關係運算子測試兩個值之間的關係。它們包括相等不等式包含關係

相等

等號運算子 == 會檢查運算元的數值是否被視為相等。

不等號運算子 != 是表達反向的捷徑:a != b 應等同於 !(a == b)

實作不等號運算子的類型必須確保遵守此規則。為了效能考量,特殊實作可能很有用,因為不等式通常可以比等式更快地被證明。

這兩個運算子都應該是可交換的,即當且僅當 b == aa == b。編譯器不會強制執行這一點,實作類型必須自行處理。

運算子 描述 範例 可多載 結合性
== 等於 1 == 2
!= 不等於 1 != 2

資訊

標準函式庫定義了 Reference#same? 作為另一個非運算子的相等性測試。它檢查參考同一性,判斷兩個值是否參考記憶體中的相同位置。

不等式

不等式運算子描述數值之間的順序。

三向比較運算子 <=> (也稱為太空船運算子) 以其回傳值的符號表達兩個元素之間的順序。

運算子 描述 範例 可多載 結合性
< 小於 1 < 2
<= 小於或等於 1 <= 2
> 大於 1 > 2
>= 大於或等於 1 >= 2
<=> 三向比較 1 <=> 2

資訊

標準函式庫定義了 Comparable 模組,它從三向比較運算子衍生出所有其他不等式運算子以及等號運算子。

涵蓋

模式匹配運算子 =~ 檢查第一個運算元的值是否與第二個運算元的值符合模式匹配。

無模式匹配運算子 !~ 表示反向。

案例涵蓋運算子 === (也稱為案例等號運算子三等號,但不精確) 檢查右側運算元是否為左側運算元所描述的集合的成員。確切的解釋會根據所涉及的資料類型而有所不同。

編譯器在 case ... when 條件 中插入此運算子。

沒有反向運算子。

運算子 描述 範例 可多載 結合性
=~ 模式匹配 "foo" =~ /fo/
!~ 無模式匹配 "foo" !~ /fo/
=== 案例涵蓋 /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)

邏輯

運算子 描述 範例 可多載 結合性
&& 邏輯 AND true && false
|| 邏輯 OR 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]?