Swift 程式語言 — Advanced Operators (2)

讓我們一起來看看什麼是高級運算符,。

Jeremy Xue
Jeremy Xue ‘s Blog
9 min readMar 18, 2020

--

Photo by Markus Spiske on Unsplash

前言:

上一篇有關高級運算符的文章在這,裡面提到了一些稍微科普了一下什麼是高級運算符,以及介紹一些關於位元運算和溢位處理的方式,若還沒看過的讀者可以先閱讀這篇:

|優先權與關聯性

運算符優先級給予某些運算符更高的優先權;這些運算符被首先應用。

運算符關聯性定義了如何將具有相同優先級的運算符結合再一起,從左側分組或是從右側分組。可以將其理解為 “它們與左側的表達式關聯” 或 “它們與右側的表達式關聯”。

在計算複合表達式的執行順序時,考慮每個運算符的優先級和關聯性是很重要的。例如,運算符優先級解釋了為什麼下列表達式等於 17。

如果以嚴格的方式從左向右閱讀,則可能預期的表達式計算為:

  • 2 加 3 等於 5
  • 5 餘數 4 等於 1
  • 1 乘 5 等於 5

然而,實際的答案為 17。較高優先級的運算符在較低優先度的運算符之前計算。在 Swift 中與 C 語言相同,餘數運算符(%)和乘法運算符(*)的優先度具有高於加法運算符(+)的優先度。因此,在考慮添加之前它們都被計算。

但是,餘數和乘法具有相同的優先級。要確定要使用的明確計算順序,你還需要考慮它們的關聯性。餘數和乘法都與左側的表達式相關聯。可以認為這是在表達式的這些部分的左邊開始添加隱式括號:

(3 % 4) 為 3,因此這等於:

(3 * 5) 為 15,因此這等於:

本次計算得出的最終答案為 17。

有關 Swift 標準庫提供的運算符的資訊,包括運算符優先級和關聯性設置的完整列表,請參考 Operator Declarations

|運算符方法

類與結構可以提供自已現有運算符的實現。這稱為現有運算符多載(overloading the existing operators)。

下面的範例展示自定義結構如何實現算術運算符(+)。算數加法運算符是二進制運算符,因為它在兩個目標上行進操作,而且因為它出現在兩個目標之間,所以被稱為中綴。

該範例為二維坐標向量(x, y)定義了 Vector2D 結構,然後定義了將 Vector2D 結構的實例添加在一起的運算符方法:

運算符方法在 Vector2D 被定義為類型方法,其方法名稱與要被多載的運算符(+)相匹配。因為加法不是向量必要的行為,所以類型方法是在 Vector2D 的擴展中定義的,而不是在 Vector2D 的主結構宣告中定義的。由於算數加法運算符是二進制運算符,所以此運算符方法採用 Vector2D 類型的兩個輸入參數,並且返回單個輸出值,也為 Vector2D 類型。

此實現中,輸入參數分別命名為 leftright 來表示在 + 運算符左側和右側的 Ventor2D 實例。該方法返回一個新的 Vector2D 實例,該實例的 xy 屬性被來自兩個 Vector2D 實例 xy 屬性之和初始化。

該類型方法可被用於現有 Vector2D 實例之間的中綴運算符:

本範例將向量 (3.0, 1.0)(2.0, 4.0) 相加在一起來產生向量 (5.0, 5.0),如下圖所示:

|前綴與後綴運算符

上面顯示的範例演示了二進制中綴運算符的自定義實現。類與結構也可以提供標準一元運算符的實現。一元運算符針對單個目標進行運算。如果它們在目標之前(例如: -a),則為前綴運算符;如果在目標之後(例如: b!),則為後綴運算符。

在定義運算符方法時,透過在 func 關鍵字編寫 prefixpostfix 修飾符來實現前綴或後綴一元運算符:

上面範例為 Ventor2D 實例實現一元減號運算符(-a)。一元減號運算符為前綴運算符,因此該方法必須使用 prefix 修飾符修飾。

對於簡單的數值,一元減號運算符將正數轉為負數,反之亦然。Ventor2D 實例的相應實現對 xy 屬性都執行此操作:

|複合分配運算符

複合賦值運算符將賦值(=)與另一個運算結合在一起。例如,加法賦值運算符將加法和賦值結合為一個操作。你在複合賦值運算符的左輸入參數類型標記為 inout,因為該參數的值將直接從運算符內部進行修改。

下面範例為 Vector2D 實例實現加法賦值運算符方法:

由於加法運算符是較早定義的,因此你不需要在此處重新實現加法運算過程。除此之外,加法賦值運算符方法採用現有加法運算符的優點,並取使用它來將左值設置為左值加上右值:

|等於運算符

默認情況下,自定義類和結構沒有具有等於運算符的實現,即等於運算符(==)和不等於運算符(!=)。通常實現 == 運算符,並且使用標準庫 != 運算符的默認實現來否定 == 運算符的結果。有兩種方式可以實現 == 運算符:你可以自己實現它,或者對於許多類型,可以要求 Swift 為你綜合實現。在這兩種情況下,都將對標準庫的 Equatable 協議添加一致性。

你可以透過實現其他中綴運算符的相同方式來提供 == 運算符的實現:

上面的範例實現了 == 運算符來檢查兩個 Vector2D 實例是否具有相同的值。在 Vector2D 的上下文中,將 “等於” 視為 “兩個實例具有相同的 x 和 y 值” 是有意義的,因此這是運算符實現所使用的邏輯。

現在,你可以使用此運算符來檢查兩個 Vector2D 實例是否相等:

在許多簡單情況下,你可以要求 Swift 為你提供等於運算符的合成實現。Swift 提供了以下幾種自定義類型的綜合實現:

  • 僅有存儲屬性的且遵循 Equatable 協議的結構
  • 僅有具有關聯類型且遵循 Equatable 協議的枚舉
  • 沒有關聯類型的枚舉

要接收 == 的綜合實現,在包含原始宣告的文件中宣告 Equatable 一致性,而不需要自己實現 == 運算符。

下面的範例為三維坐標向量(x, y, z)定義了 Vector3D 的結構,類似於 Vector2D 的結構。由於 x, yz 屬性均為 Equatable 類型,因此 Vector3D 接收等於運算符的綜合實現。

|自定義運算符

除了 Swift 提供的標準運算符以外,你還可以宣告和實現自己所自定義的運算符。有關可用於定義自定義運算符的字符列表,請參考 Operators

使用 operator 關鍵字在全局級別宣告新的運算符,並使用 prefixinfixpostfix 修飾符標記:

上面的範例定義了新的前綴運算符 +++。該運算符在 Swift 中沒有現有的意義。因此在下面使用 Vector2D 實例的特定上下文中為其賦予了自定義的意義。就本範例而言,+++ 被視為新的 “前綴加倍” 運算符。透過使用先前定義的加法賦值運算符將向量與其自身相加,它可以將 Vector2D 實例的 x 和 y 值加倍。要實現 +++ 運算符,向 Vector2D 添加一個稱為 +++ 的類型方法,如下所示:

|自定義中綴運算符的優先級

自定義中綴運算符每個都屬於一個優先級組。優先級組指定了一個運算符相對於其他中綴運算符的優先級,以及該運算符的關聯性。

未明確放置在優先級組中的自定義中綴運算符將被賦予默認優先級組,其優先度立即高於三元條件運算符的優先級。

下面範例定義了名為 +- 的新自定義中綴運算符,該運算符屬於優先級組 AdditionPrecedence

此運算符將兩個向量的 x 值相加,然後從第一個向量中減去第二個向量的 y 值。因為它本質上是一個 “加法” 運算符,所以它被賦予與加法中綴運算符(例如 +-)相同的優先級組。

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

Hi, I’m Jeremy. [好想工作室 — iOS Developer]