swapTwoValues(_:_:)
函数和 Stack
适用于任意类型。不过,如果能对泛型函数或泛型类型中添加特定的类型约束,这将在某些情况下非常有用。类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。
例如,Swift 的 Dictionary
类型对字典的键的类型做了些限制。在 字典的描述 中,字典键的类型必须是可哈希(hashable
)的。也就是说,必须有一种方法能够唯一地表示它。字典键之所以要是可哈希的,是为了便于检查字典中是否已经包含某个特定键的值。若没有这个要求,字典将无法判断是否可以插入或替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
这个要求通过 Dictionary
键类型上的类型约束实现,它指明了键必须遵循 Swift 标准库中定义的 Hashable
协议。所有 Swift 的基本类型(例如 String
、Int
、Double
和 Bool
)默认都是可哈希的。
当自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。像 可哈希(hashable)
这种抽象概念根据它们的概念特征来描述类型,而不是它们的具体类型。
类型约束语法
在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束。下面将展示泛型函数约束的基本语法(与泛型类型的语法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // 这里是泛型函数的函数体部分 }
上面这个函数有两个类型参数。第一个类型参数 T
必须是 SomeClass
子类;第二个类型参数 U
必须符合 SomeProtocol
协议。
类型约束实践
这里有个名为 findIndex(ofString:in:)
的非泛型函数,该函数的功能是在一个 String
数组中查找给定 String
值的索引。若查找到匹配的字符串,findIndex(ofString:in:)
函数返回该字符串在数组中的索引值,否则返回 nil
:
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } return nil }
findIndex(ofString:in:)
函数可以用于查找字符串数组中的某个字符串值:
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] if let foundIndex = findIndex(ofString: "llama", in: strings) { print("The index of llama is \(foundIndex)") } // 打印“The index of llama is 2”
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 T
替换 String
类型来写出具有相同功能的泛型函数 findIndex(_:_:)
。
下面展示了 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 }
上面所写的函数无法通过编译。问题出在相等性检查上,即 "if value == valueToFind
"。不是所有的 Swift 类型都可以用等式符(==
)进行比较。例如,如果你自定义类或结构体来描述复杂的数据模型,对于这个类或结构体而言,Swift 无法明确知道“相等”意味着什么。正因如此,这部分代码无法保证适用于任意类型 T
,当你试图编译这部分代码时就会出现相应的错误。
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 Equatable
协议,该协议要求任何遵循该协议的类型必须实现等式符(==
)及不等符(!=
),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 Equatable
协议。
遵循 Equatable
协议的类型都可以安全地用于 findIndex(of:in:)
函数,因为其保证支持等式操作符。为了说明这个事情,当定义一个函数时,你可以定义一个 Equatable
类型约束作为类型参数定义的一部分:
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } return nil }
findIndex(of:in:)
类型参数写做 T: Equatable
,也就意味着“任何符合 Equatable
协议的类型 T
”。
findIndex(of:in:)
函数现在可以成功编译了,并且适用于任何符合 Equatable
的类型,如 Double
或 String
:
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25]) // doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中 let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"]) // stringIndex 类型为 Int?,其值为 2