swift入门22 范型

Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code. In fact, you’ve been using generics throughout the Language Guide, even if you didn’t realize it. For example, Swift’s Array and Dictionary types are both generic collections. You can create an array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift.

范型是swift最强大的特性之一。swift的很多标准库都使用了范型。其实,在语言指导中你已经使用了范型,只是你没有发现。比如,swift的数组和字典类型都是范型集合。你可以创建一个保存int值的数组,或者保存string值的数组,或者保存其他类型的数组。

范型解决的问题

Here’s a standard, nongeneric function called swapTwoInts(::), which swaps two Int values:

下面是一个典型的,非范型的函数swapTwoInts(::)—用于交换两个int值:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

The swapTwoInts(::) function swaps the original value of b into a, and the original value of a into b. You can call this function to swap the values in two Int variables:

swapTwoInts(::)函数把b的原始值给了a,再把a的原始值给了b。

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"

The swapTwoInts(::) function is useful, but it can only be used with Int values. If you want to swap two String values, or two Double values, you have to write more functions, such as the swapTwoStrings(::) and swapTwoDoubles(::) functions shown below:

swapTwoInts(::)函数很有用,但是它只能用于int值。如果你想交换两个string值,或者两个double值。你需要写更多的函数,比如swapTwoStrings(::) 和swapTwoDoubles(::):

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

You may have noticed that the bodies of the swapTwoInts(::), swapTwoStrings(::), and swapTwoDoubles(::) functions are identical. The only difference is the type of the values that they accept (Int, String, and Double).

你可能已经注意到了, swapTwoInts(::), swapTwoStrings(::), 和 swapTwoDoubles(::)这三个函数的函数体是一样的。不同的只是它们接收的类型(Int, String, 和 Double).

It’s more useful, and considerably more flexible, to write a single function that swaps two values of any type. Generic code enables you to write such a function. (A generic version of these functions is defined below.)

如果能只用一个函数就能交换两个值(而不管它们是什么类型的)的话,那将非常有用也更灵活。范型就可以做到这一点。

范型函数

Generic functions can work with any type. Here’s a generic version of the swapTwoInts(::) function from above, called swapTwoValues(::):

范型函数可以用于任何类型。下面是swapTwoInts(::)函数的范型版本:swapTwoValues(::):

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

The body of the swapTwoValues(::) function is identical to the body of the swapTwoInts(::) function. However, the first line of swapTwoValues(::) is slightly different from swapTwoInts(::). Here’s how the first lines compare:

swapTwoValues(::)函数的函数体和swapTwoInts(::)函数的函数体一样。但是,swapTwoValues(::)的第一行和swapTwoInts(::)的第一行有些许不同。下面是这两个函数的第一行的对比:

func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)

The generic version of the function uses a placeholder type name (called T, in this case) instead of an actual type name (such as Int, String, or Double). The placeholder type name doesn’t say anything about what T must be, but it does say that both a and b must be of the same type T, whatever T represents. The actual type to use in place of T is determined each time the swapTwoValues(::) function is called.

范型版本的函数使用了一个占位符类型名(这里称为T),而不是一个真正的类型名(比如int,string,double)。占位符类型名并没有说明T必须是什么,它只是说a和b必须为某个类型T,无论T代表什么。用来代替T的真实类型在swapTwoValues(::)函数调用的时候才确定。

The other difference between a generic function and a nongeneric function is that the generic function’s name (swapTwoValues(::)) is followed by the placeholder type name (T) inside angle brackets (<T>). The brackets tell Swift that T is a placeholder type name within the swapTwoValues(::) function definition. Because T is a placeholder, Swift doesn’t look for an actual type called T.

范型函数和非范型函数的其他区别是:范型函数的名称(swapTwoValues(::))后面跟着一对尖括号,括号内包含着占位符类型名T(<T>).这对尖括号告诉swift,T是swapTwoValues(::)函数定义内的一个占位符类型名。由于T是一个占位符,因此,swift便不会去查询一个真实的类型T。

The swapTwoValues(::) function can now be called in the same way as swapTwoInts, except that it can be passed two values of any type, as long as both of those values are of the same type as each other. Each time swapTwoValues(::) is called, the type to use for T is inferred from the types of values passed to the function.

swapTwoValues(::)函数现在可以像调用swapTwoInts那样被调用,除了它可以接收任何类型,只要这两个值的类型都一致。每次调用swapTwoValues(::)的时候,用于T的类型指向传递到函数的类型。

In the two examples below, T is inferred to be Int and String respectively:

下面的两个例子中,T分别指向int和string:

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
 
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"

类型参数

In the swapTwoValues(::) example above, the placeholder type T is an example of a type parameter. Type parameters specify and name a placeholder type, and are written immediately after the function’s name, between a pair of matching angle brackets (such as <T>).

在上面的swapTwoValues(::)中,占位符类型 T是一个类型参数的例子。类型参数指定一个占位符类型,并命名,写在函数名称的后面的一对尖括号内(比如<T>)。

Once you specify a type parameter, you can use it to define the type of a function’s parameters (such as the a and b parameters of the swapTwoValues(::) function), or as the function’s return type, or as a type annotation within the body of the function. In each case, the type parameter is replaced with an actual type whenever the function is called. (In the swapTwoValues(::) example above, T was replaced with Int the first time the function was called, and was replaced with String the second time it was called.)

一旦你指定了一个类型参数,你就可以用它来定义一个函数参数的类型(就像swapTwoValues(::)函数的a和b参数),或者定义为 函数的返回类型,或者是函数体内的类型注释。函数每次被调用的时候,类型参数都被真正的类型替代。比如在swapTwoValues(::)中,第一次调用的时候,T被int替代,第二次调用的时候,T被string替代。

You can provide more than one type parameter by writing multiple type parameter names within the angle brackets, separated by commas.

你可以提供多个类型参数,只需写上多个类型参数名,中间用逗号隔开即可。

类型参数命名

In most cases, type parameters have descriptive names, such as Key and Value in Dictionary<Key, Value> and Element in Array<Element>, which tells the reader about the relationship between the type parameter and the generic type or function it’s used in. However, when there isn’t a meaningful relationship between them, it’s traditional to name them using single letters such as T, U, and V, such as T in the swapTwoValues(::) function above.

多数情况下,类型参数都有描述性的名称,就像字典中的键和值,数组中的元素一样,这些名称可以告诉读者范型类型或范型函数与类型参数之间的关系。然而,当它们之间没有一个有意义的关系的时候,传统的做法是使用单个的字母,比如T, U,和 V。

范型类型

In addition to generic functions, Swift enables you to define your own generic types.

除了范型函数,swift还允许你定义自己的范型类型。

This section shows you how to write a generic collection type called Stack. A stack is an ordered set of values, similar to an array, but with a more restricted set of operations than Swift’s Array type. An array allows new items to be inserted and removed at any location in the array. A stack, however, allows new items to be appended only to the end of the collection (known as pushing a new value on to the stack). Similarly, a stack allows items to be removed only from the end of the collection (known as popping a value off the stack).

下面会展示如何写一个范型集合类型,Stack(栈)。栈是一种值的有序集合,跟数组类似,但是比swift的数组多了些操作。数组允许在数组中的任何位置插入和删除新的项(item).但是,栈只能在集合的末尾添加新的元素(称为入栈)。同样,栈也只运行在栈的末尾删除元素(称为出栈).

The illustration below shows the push and pop behavior for a stack:

下图演示了入栈和出栈操作:

1 There are currently three values on the stack.

2 A fourth value is pushed onto the top of the stack.

3 The stack now holds four values, with the most recent one at the top.

4 The top item in the stack is popped.

5 After popping a value, the stack once again holds three values.

1 当前栈中有三个值

2 第四个值入栈

3 该栈现在有四个值,最新入栈的值在最上面

4 最上面的项被出栈

5 出栈之后,该栈还是有三个值。

Here’s how to write a nongeneric version of a stack, in this case for a stack of Int values:

下面是非范型版的栈—只保存int值:

struct IntStack {
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

This structure uses an Array property called items to store the values in the stack. Stack provides two methods, push and pop, to push and pop values on and off the stack. These methods are marked as mutating, because they need to modify (or mutate) the structure’s items array.

这个结构用一个数组属性items来保存栈的值。栈有两个方法,push 和 pop,用于入栈和出栈。这些方法都是mutating的,因为它们需要修改结构的items数组。

The IntStack type shown above can only be used with Int values, however. It would be much more useful to define a generic Stack class, that can manage a stack of any type of value.

IntStack类型只能用于int值。如果能定义一个能处理任何类型的范型栈类,那将非常有用。

下面就是范型版本的栈:

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

Note how the generic version of Stack is essentially the same as the nongeneric version, but with a type parameter called Element instead of an actual type of Int.

注意,范型版本的栈(stack)和非范型版本本质上是一样的,只是用了类型参数Element替换了真正的类型int。

Because it is a generic type, Stack can be used to create a stack of any valid type in Swift, in a similar manner to Array and Dictionary.

由于它是范型类型,Stack可以用来创建swift中任何有效类型的栈,就像数组和字典一样。

You create a new Stack instance by writing the type to be stored in the stack within angle brackets. For example, to create a new stack of strings, you write Stack<String>():

创建一个Stack实例,只需在尖括号内写上要存储的类型即可。比如,要创建一个string的值,可以这么写Stack<String>():

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings

stackOfStrings实例入栈了四个元素之后看起来像这样:

Popping a value from the stack removes and returns the top value, "cuatro":

把cuatro出栈:

let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings

出栈之后

拓展范型类型

When you extend a generic type, you do not provide a type parameter list as part of the extension’s definition. Instead, the type parameter list from the original type definition is available within the body of the extension, and the original type parameter names are used to refer to the type parameters from the original definition.

当你拓展一个范型类型时,你不需要提供一个类型参数列表。

The following example extends the generic Stack type to add a read-only computed property called topItem, which returns the top item on the stack without popping it from the stack:

下面的例子拓展了范型Stack类型,增加了一个只读的计算属性topItem,此属性返回栈中最上面的项,而不用把它出栈:

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

Note that this extension doesn’t define a type parameter list. Instead, the Stack type’s existing type parameter name, Element, is used within the extension to indicate the optional type of the topItem computed property.

注意,这个拓展并没有定义一个参数类型列表。而是用Stack类型已有的类型参数名,Element,来标示可选的计算属性topItem。

The topItem computed property can now be used with any Stack instance to access and query its top item without removing it.

计算属性topItem现在可以用于任何Stack实例。

if let topItem = stackOfStrings.topItem {
    print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."

类型限制

The swapTwoValues(::) function and the Stack type can work with any type. However, it is sometimes useful to enforce certain type constraints on the types that can be used with generic functions and generic types. Type constraints specify that a type parameter must inherit from a specific class, or conform to a particular protocol or protocol composition.

swapTwoValues(::)函数和Stack类型可以处理任何类型。但是,有时候,强制范型函数和范型类型能使用的类型是很有用的。类型限制指定一个类型参数必须继承自一个特定的类,或者遵循一个特定的协议或多个协议。

For example, Swift’s Dictionary type places a limitation on the types that can be used as keys for a dictionary. As described in Dictionaries, the type of a dictionary’s keys must be hashable.

比如,swift的字典类型就限制了能用作字典的键的类型。字典的键的类型必须是可哈希的(hashable).

This requirement is enforced by a type constraint on the key type for Dictionary, which specifies that the key type must conform to the Hashable protocol, a special protocol defined in the Swift standard library. All of Swift’s basic types (such as String, Int, Double, and Bool) are hashable by default.

这个要求强制字典的键类型必须遵循Hashable协议。swift的继承类型(比如string,int,double和bool)都是可哈希的。

类型限制语法

You write type constraints by placing a single class or protocol constraint after a type parameter’s name, separated by a colon, as part of the type parameter list. The basic syntax for type constraints on a generic function is shown below (although the syntax is the same for generic types):

要定义类型限制,只要在类型参数列表中的类型参数名称后面加上类或协议限制,用冒号隔开。下面是范型函数的类型限制的基础语法(范型类型的语法与此相同):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}

The hypothetical function above has two type parameters. The first type parameter, T, has a type constraint that requires T to be a subclass of SomeClass. The second type parameter, U, has a type constraint that requires U to conform to the protocol SomeProtocol.

上面的函数有两个类型参数。第一个类型参数,T,有一个类型限制---要求T必须是SomeClass的子类。第二个类型参数,U,有一个类型限制---要求U必须遵循协议SomeProtocol。

类型限制实战

Here’s a nongeneric function called findIndex(ofString:in:), which is given a String value to find and an array of String values within which to find it. The findIndex(ofString:in:) function returns an optional Int value, which will be the index of the first matching string in the array if it is found, or nil if the string cannot be found:

下面是一个非范型函数findIndex(ofString:in:),该函数用于从一个string值的数组中找到一个给定的string值。findIndex(ofString:in:)函数返回一个可选的int值,该int值是找到的第一个元素的下标,如果没找到指定的string值,则返回nil:

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
    print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"

Here’s how you might expect a generic version of findIndex(ofString:in:), called findIndex(of:in:), to be written. Note that the return type of this function is still Int?, because the function returns an optional index number, not an optional value from the array. Be warned, though—this function doesn’t compile, for reasons explained after the example:

下面是findIndex(ofString:in:)的范型版本:findIndex(of:in:)。返回类型依然是Int?。

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

This function doesn’t compile as written above. The problem lies with the equality check, “if value == valueToFind”. Not every type in Swift can be compared with the equal to operator (==). If you create your own class or structure to represent a complex data model, for example, then the meaning of “equal to” for that class or structure isn’t something that Swift can guess for you. Because of this, it isn’t possible to guarantee that this code will work for every possible type T, and an appropriate error is reported when you try to compile the code.

上面写的这个方法其实无法编译。这是因为相等检查,“if value == valueToFind”。swift中,不是每个类型都能用相等操作符来进行比较。如果你创建了你自己的类或结构来代表一个复杂的数据结构,那么,swift其实并不知道那个类或结构的“相等”是什么意思。因此,就无法保证对于任何可能的类型T此函数都能运行,如果你编译这歌函数,将会报错。

All is not lost, however. The Swift standard library defines a protocol called Equatable, which requires any conforming type to implement the equal to operator (==) and the not equal to operator (!=) to compare any two values of that type. All of Swift’s standard types automatically support the Equatable protocol.

swift标准库定义了一个协议Equatable,该协议要求任何遵循此协议的类型都实现相等操作符(==)和不相等操作符(!=)来比较两个此类型的值。swift的所有标准类型都自动支持Equatable协议。

Any type that is Equatable can be used safely with the findIndex(of:in:) function, because it is guaranteed to support the equal to operator. To express this fact, you write a type constraint of Equatable as part of the type parameter’s definition when you define the function:

Equatable的任何类型都可以安全地用于findIndex(of:in:)函数,因为它支持相等操作符。

func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

The single type parameter for findIndex(of:in:) is written as T: Equatable, which means “any type T that conforms to the Equatable protocol.”

findIndex(of:in:)的类型参数写作 T: Equatable,这意味着“遵循Equatable协议的任意类型T".

The findIndex(of:in:) function now compiles successfully and can be used with any type that is Equatable, such as Double or String:

findIndex(of:in:)函数现在可以成功编译了,并且可以用于任何Equatable的类型,比如double和string:

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2

关联类型

When defining a protocol, it is sometimes useful to declare one or more associated types as part of the protocol’s definition.Associated types are specified with the associatedtype keyword.

当定义一个协议的时候,有时候声明一个或多个关联类型十分有用。关联类型用associatedtype关键字指定。

关联类型实战

Here’s an example of a protocol called Container, which declares an associated type called Item:

下面是一个例子,定义了一个协议Container,协议声明了一个关联类型item:

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

The Container protocol defines three required capabilities that any container must provide:

It must be possible to add a new item to the container with an append(_:) method.

It must be possible to access a count of the items in the container through a count property that returns an Int value.

It must be possible to retrieve each item in the container with a subscript that takes an Int index value.

Container协议定了三条功能,任何遵循此协议的类都必须提供:

它必须能用 append(_:) 增加一个新的item到container。

它必须能通过count属性获取container的item的数量。

它必须能通过下标获取container的每个item。

This protocol doesn’t specify how the items in the container should be stored or what type they are allowed to be. The protocol only specifies the three bits of functionality that any type must provide in order to be considered a Container. A conforming type can provide additional functionality, as long as it satisfies these three requirements.

这个协议没有指定container中的items该怎么保存,或它们可以是什么类型。该协议只指定了三点功能。

下面是之前的非范型IntStack类型,让他遵循Container协议:

struct IntStack: Container {
    // original IntStack implementation
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // conformance to the Container protocol
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

你也可以使范型Stack类型遵循Container协议:

struct Stack<Element>: Container {
    // original Stack<Element> implementation
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

范型where从句(Generic Where Clauses)

Type constraints, as described in Type Constraints, enable you to define requirements on the type parameters associated with a generic function or type.

类型限制,让你可以给范型函数或类型的类型参数规定一些要求。

It can also be useful to define requirements for associated types. You do this by defining a generic where clause. A generic where clause enables you to require that an associated type must conform to a certain protocol, or that certain type parameters and associated types must be the same. A generic where clause starts with the where keyword, followed by constraints for associated types or equality relationships between types and associated types. You write a generic where clause right before the opening curly brace of a type or function’s body.

也可以给关联类型规定一些要求。要实现这个可以使用范型where从句。一个范型where从句让你可以要求一个关联类型必须遵循一个特定的协议,或者要求类型参数和关联类型必须一样。一个范型where从句由where关键字开始,后面跟着关联类型的限制或者是相等的类型和关联类型。范型where从句要写在类型或函数体的左大括号前边。

The example below defines a generic function called allItemsMatch, which checks to see if two Container instances contain the same items in the same order. The function returns a Boolean value of true if all items match and a value of false if they do not.

下面的例子定义了一个范型函数allItemsMatch,该函数用于检测两个Container实例是否包含相同顺序的相同的项。如果所有的项都满足条件,该函数返回true,否则返回false。

The two containers to be checked do not have to be the same type of container (although they can be), but they do have to hold the same type of items. This requirement is expressed through a combination of type constraints and a generic where clause:

要被检查的两个containers不一定要是相同类型的container(虽然可以是),但是它们必须包含相同类型的items。下面例子用类型限制和一条范型where从句来规定:

func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable {
        
        // Check that both containers contain the same number of items.
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        // Check each pair of items to see if they are equivalent.
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // All items match, so return true.
        return true
}

This function takes two arguments called someContainer and anotherContainer. The someContainer argument is of type C1, and the anotherContainer argument is of type C2. Both C1 and C2 are type parameters for two container types to be determined when the function is called.

这个函数接收两个参数,分别是someContainer 和 anotherContainer。参数someContainer的类型是C1,参数anotherContainer的类型是C2。

The following requirements are placed on the function’s two type parameters:

C1 must conform to the Container protocol (written as C1: Container).

C2 must also conform to the Container protocol (written as C2: Container).

The Item for C1 must be the same as the Item for C2 (written as C1.Item == C2.Item).

The Item for C1 must conform to the Equatable protocol (written as C1.Item: Equatable).

该方法等两个类型参数要符合以下要求:

C1必须遵循Container协议(写作 C1: Container)。

C2必须遵循Container协议(写作 C2: Container)。

C1的item必须和C2的item一样(写作 C1.Item == C2.Item)。

C1的item必须遵循Equatable协议(写作 C1.Item: Equatable)。

The first and second requirements are defined in the function’s type parameter list, and the third and fourth requirements are defined in the function’s generic where clause.

第一点和第二点要求是在函数的类型参数列表中定义的,第三点和第四点要求是在函数的范型where从句中定义的。

These requirements mean:

someContainer is a container of type C1.

anotherContainer is a container of type C2.

someContainer and anotherContainer contain the same type of items.

The items in someContainer can be checked with the not equal operator (!=) to see if they are different from each other.

这些要求意味着:

someContainer 是一个C1类型的container。

anotherContainer 是一个C2类型的container。

someContainer 和 anotherContainer包含有相同类型的items。

someContainer中的items可以用不等操作符!=来检查,看看它们之间是否互不相同。

The third and fourth requirements combine to mean that the items in anotherContainer can also be checked with the != operator, because they are exactly the same type as the items in someContainer.

第三点和第四点要求一起意味着anotherContainer的items也可以用不等操作符!=来检查,因为它们的类型跟someContainer的items的类型一样。

These requirements enable the allItemsMatch(::) function to compare the two containers, even if they are of a different container type.

这些要求使得allItemsMatch(::)函数可以比较两个containers,即使它们是不同的container类型。

The allItemsMatch(::) function starts by checking that both containers contain the same number of items. If they contain a different number of items, there is no way that they can match, and the function returns false.

allItemsMatch(::)函数一开始就检查两个containers包含的items的数量是否一致。如果不一致,那么它们就不可能匹配,于是函数返回false。

After making this check, the function iterates over all of the items in someContainer with a for-in loop and the half-open range operator (..<). For each item, the function checks whether the item from someContainer isn’t equal to the corresponding item in anotherContainer. If the two items aren’t equal, then the two containers do not match, and the function returns false.

这步检查完后,此函数会用一个for-in循环迭代someContainer的items的所有项。对于每个item,函数会检查someContainer的item是否不等于对应的anotherContainer的item。如果不想等,那么这两个container就不匹配,函数返回false。

If the loop finishes without finding a mismatch, the two containers match, and the function returns true.

如果循环没有找到不匹配的项,则表示两个container是匹配的,函数返回true。

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
 
var arrayOfStrings = ["uno", "dos", "tres"]
 
if allItemsMatch(stackOfStrings, arrayOfStrings) {
    print("All items match.")
} else {
    print("Not all items match.")
}
// Prints "All items match."

The example above creates a Stack instance to store String values, and pushes three strings onto the stack. The example also creates an Array instance initialized with an array literal containing the same three strings as the stack. Even though the stack and the array are of a different type, they both conform to the Container protocol, and both contain the same type of values. You can therefore call the allItemsMatch(::) function with these two containers as its arguments. In the example above, the allItemsMatch(::) function correctly reports that all of the items in the two containers match.

上面的代码创建了一个Stack实例来保存string值,并且把三个string入栈。代码还创建了一个数组实例,数组被初始化为包含三个与stack实例包含的string值一样的三个string值。虽然statck和数组是不同的类型,但是它们都遵循Container协议,也都包含相同类型的值。所以你可以调用allItemsMatch(::)函数,用这两个container(容器)做参数。在上面的代码中,allItemsMatch(::) 正确地计算出两个容器中的所有项都匹配。

使用范型where从句的拓展

You can also use a generic where clause as part of an extension. The example below extends the generic Stack structure from the previous examples to add an isTop(_:) method.

你也可以在一个拓展中使用范型where从句。下面的例子拓展了范型Stack结构,添加了isTop(_:)方法:

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

This new isTop(:) method first checks that the stack isn’t empty, and then compares the given item against the stack’s topmost item. If you tried to do this without a generic where clause, you would have a problem: The implementation of isTop(:) uses the == operator, but the definition of Stack doesn’t require its items to be equatable, so using the == operator results in a compile-time error. Using a generic where clause lets you add a new requirement to the extension, so that the extension adds the isTop(_:) method only when the items in the stack are equatable.

这个新的isTop(:) 方法首先检查栈(stack)不为空,接着比较给定的item和栈的最顶端端item。如果你不用范型where从句,那么可能会有一个问题:isTop(:)方法使用了== 操作符,但是Stack的定义并不要求他的项(items)是equatable的,所以使用==操作符就产生一个编译时错误。使用范型where从句让你给这个拓展增加一个新的要求,即只有stack中的item是equatable的时候,此拓展才添加isTop(_:)方法。

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// Prints "Top element is tres."

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // 报错

You can use a generic where clause with extensions to a protocol. The example below extends the Container protocol from the previous examples to add a startsWith(_:) method.

你可以为一个协议中添加带有范型where从句的拓展。下面的例子拓展了Container协议,增加一个 startsWith(_:)方法:

extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

The startsWith(:) method first makes sure that the container has at least one item, and then it checks whether the first item in the container matches the given item. This new startsWith(:) method can be used with any type that conforms to the Container protocol, including the stacks and arrays used above, as long as the container’s items are equatable.

startsWith(:)方法首先确保container至少有一个item,接着它检查container的第一的item与给定的item是否匹配。这个新的startsWith(:)方法可以用于任何遵循Container协议的类型,包括上面的栈和数组,只要它们的items是equatable的。

if [9, 9, 9].startsWith(42) {
    print("Starts with 42.")
} else {
    print("Starts with something else.")
}
// Prints "Starts with something else."

The generic where clause in the example above requires Item to conform to a protocol, but you can also write a generic where clauses that require Item to be a specific type. For example:

上面例子的范型where从句要求item必须遵循一个协议,你也可以写一个范型where从句,要求item必须是特定的类型。比如:

extension Container where Item == Double {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += self[index]
        }
        return sum / Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// Prints "648.9"

This example adds an average() method to containers whose Item type is Double. It iterates over the items in the container to add them up, and divides by the container’s count to compute the average. It explicitly converts the count from Int to Double to be able to do floating-point division.

这个例子给container添加了一个average()方法,其中item的类型是double。此方法迭代container中的item,并累加,然后除以container的个数,算出平均数。此方法显式的把int转换为double,使得它可以进行浮点除法运算。

You can include multiple requirements in a generic where clause that is part of an extension, just like you can for a generic where clause that you write elsewhere. Separate each requirement in the list with a comma.

你可以在一条范型where从句中包含多个条件。每个条件用逗号隔开。

推荐阅读更多精彩内容

  • Generic codeenables you to write flexible, reusable funct...
    绿色庄园阅读 100评论 0 0
  • 公交车上,我单曲循环着周杰伦的《晴天》。 这么多年过去了,依然沒换过手机里的歌。 大学毕业后,我就和赵燕分别工作在...
    蔡不帅阅读 310评论 16 10
  • 斯卡恩(Skagen)一译“斯卡晏”,是丹麦日德兰半岛东北的城镇和港口,位于半岛北端的海岬上,临卡特加特海峡。斯卡...
    慕溪北欧旅游阅读 289评论 0 1
  • 下午四五点钟,办公室灯也没开全,加之大半的遮阳帘还没有拉起,真是闷极了,我也三心两意地翘着二郎腿,颠儿着腿,盯着屏...
    何者梦也阅读 66评论 0 1
  • 那是我在宁乡上小学时和哥哥一块儿拍的照片,那是我大概是六七岁时拍的照片。 那时,每当放月假妈妈来接我时,我...
    0333娇阅读 30评论 0 1