Swift 程式語言 — Generics(3)
讓我們看看如何在泛型中使用 where 子句吧!
前言:
如果還沒有看過泛型相關概念的讀者,可以參考我的前兩篇文章:
|泛型 Where 子句
如類型約束中所述,類型約束使你可以定義與泛型函數,下標或類型關聯的類型參數要求。
定義關聯類型的要求也很有用,你可以透過定義泛型 where 子句來做到。泛型 where
子句使你能夠要求關聯類型必須遵循某個協議,或者某些類型參數和關聯類型必須相同。泛型 where
子句以 where
關鍵字開頭,後面接著關聯類型的約束或關聯類型之間的相等關係。你可以在類型或函數主體的大括號前寫一個泛型 where
子句。
下面的範例定義了一個名為 allItemMatch
的泛型函數,該函數檢查兩個 Container
實例是否包含相同順序的相同項目。如果所有項目都匹配,則該函數返回 Bool
值 true
,否則返回 false
。
要被檢查的兩個容器不必是相同類型的容器(雖然它可以是),但是它們必須容納相同類型的項目。透過類型約束和泛型 where
子句的組合來表達此要求:
該函數接收兩個參數 someContainer
和 anotherContainer
。其中 someContainer
參數類型為 C1
,而 anotherContainer
參數類型為 C2
。C1
和 C2
都是用於在調用函數時確定兩個容器類型的類型參數。
以下是對於函數的兩個類型參數的要求:
- C1 必須遵循
Container
協議(寫為C1: Container
)。 - C2 也必須遵循
Container
協議(寫為C1: Container
)。 - C1 的
Item
必須與 C2 的Item
相同(寫為C1.Item == C2.Item
)。 - C1 的項目必須遵循
Equatable
協議(寫為C1.Item: Equatable
)。
第一、二項要求在函數的類型參數列表中定義,而第三、四項的要求在函數的泛型 where
子句中定義。
這些要求意味著:
someContainer
是C1
類型的容器。anotherContainer
是C2
類型的容器。someContainer
和anotherContainer
包含相同類型的項目。- 可以使用不等於運算符(
!=
)檢查someContainer
中的項目,來查看它們是否彼此不同。
第三、四個要求結合後,意味著還可以使用 !=
運算符檢查 anotherContainer
中的項目,因為它們與 someContainer
中的項目類型完全相同。
這些要求使 allItemsMatch(_:_:)
函數可以比較兩個容器,即使它們是不同的容器也是如此。
allItemsMatch(_:_:)
函數透過檢查兩個容器包含相同數量的項目開始。如果它們包含不同數量的項目,則無法匹配它們,並且函數返回 false
。
進行此檢查後,該函數使用 for-in
循環和半開範圍運算符(..<
)對 someContainer
中的所有項目進行迭代。對於每個項目,該函數都會檢查 someContainer
中的項目是否不等於 anotherContainer
中的相應項目。如果兩個項目不相等,則兩個容器不匹配,該函數返回 false
。
如果循環結束而沒有找到不匹配項目,則兩個容器匹配,並且該函數返回 true
。
allItemsMatch(_:_:)
實際效果如下:
上方的範例創建一個 Stack
實例來存儲 String
值,並將三個 String
壓入堆棧。該範例還創建一個 Array
實例,該實例用一個 Array
常數初始化,該常數包含與堆棧相同的三個 String
。即使堆棧和數組的類型不同,它們都遵循 Container
協議,並且都包含相同類型的值。因此,你可以使用這兩個容器作為參數來調用 allItemsMatch(_:_:)
函數。在上面的範例中,allItemsMatch(_:_:)
函數正確的回報兩個容器中的所有項目都匹配。
|具有泛型 Where 子句的擴展
你還可以使用泛型 where
子句作為擴展的一部分。下面的範例擴展了之前的泛型的 Stack
結構,以添加 isTop(_:)
方法。
這個新的 isTop(_:)
方法首先檢查堆棧是否為空,然後將給定的項目與堆棧中最上方的項目進行比較。如果嘗試在沒有泛型 where
子句的情況下執行此操作,則會遇到問題:isTop(:_)
的實現使用 ==
運算符,但是 Stack
的定義不需要其項目可相等,因此使用 ==
運算符會導致編譯時錯誤。使用泛型 where
子句讓你添加新的要求,以便擴展僅在堆棧中的項目為可相等時才添加 isTop(_:)
。
這裡是 isTop(:_)
方法實際效果如下:
如果你嘗試在元素為不可相等的堆棧上調用 isTop(_:)
方法,則會出現編譯時錯誤。
你可以使用帶有協議擴展的泛型 where
子句。下面的範例擴展了 Container
協議,以添加 startsWith(_:)
方法:
startsWith(_:)
方法首先確保容器中至少有一個項目,然後檢查容器中的第一個項目是否與給定的項目匹配。這個新的 startsWith(_:)
方法可以用於符合 Container
協議的任何類型,包括上面使用的堆棧和數組,只要容器的項目是可相等的即可。
上面範例中的泛型 where
子句要求 Item
遵循協議,但是你也可以編寫一個泛型的 where
子句,要求 Item
為特定類型。例如:
這個範例向 Item
類型為 Double
的容器添加 average()
方法。其迭代容器中的項目來將其相加,然後除以容器中的數量來計算平均值。它將計數從 Int
顯示轉換為 Double
來能夠進行浮點數除法。
你可以在擴展的一部分的泛型 where
子句包含多個需求,就像你在其他地方編寫的泛型 where
子句一樣。用逗號分隔列表中的每個要求。
|具有泛型 where 子句的關聯類型
你可以在關聯類型上包含泛型 where
子句。例如,假設你想要創建一個包含迭代器(Iterator)的 Container
版本,像是 Sequence
協議在標準庫中使用的版本。這是你的寫法:
迭代器上的泛型 where
子句要求迭代器必須便利容器項目中具有項目類型的元素,而不管迭代器的類型。透過 makeIterator()
函數可以訪問容器的迭代器。
對一個從另一個協議繼承的協議,可以透過在協議宣告包含泛型 where
子句來對繼承的關聯類型添加約束。例如,下面程式碼宣告了 ComparableContainer
協議,該協議要求 Item
符合 Comparable
:
|泛型下標
下標是可以被泛型的,並且它們可以包含泛型 where
子句。你在 subscript
後的尖括號內編寫佔位符類型名稱,並在下標正文的左大括號前寫一個泛型 where
子句。例如:
容器協議的這個擴展添加了一個下標,該下標採用索引序列,並且返回一個包含每個給定索引項目的陣列。該泛型下標受以下約束:
- 尖括號中的泛型參數的
Indices
必須遵循標準庫中的Sequence
協議的類型。 - 下標採用單個參數
indices
,該參數是Indices
類型的實例。 - 泛型
where
子句要求序列的迭代器必須遍歷Int
類型的元素。這樣可以確保序列中的索引與用於容器的索引具有相同的類型。
總而言之,這些約束意味著索引參數傳遞的值是整數序列。