23.泛型
泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
1.泛型所解决的问题
//先看一个示例 func swapTwoInts(inout a: Int, inout _ b: Int) { let temporaryA = a; a = b; b = temporaryA; }
上面的函数实现了交换两个 Int 变量的值的功能,但是它只能交换 Int 值,如果你想要交换两个 String 值或者 Double值,就不得不写更多的函数。
2.泛型函数
//问题解决 func swapTwoValues<T>(inout a: T, inout _ b: T) { //swap(&a , &b); //系统的函数,等效于下面三行代码 let temporaryA = a; a = b; b = temporaryA; } //交换Int型 var someInt = 3; var anotherInt = 107; swapTwoValues(&someInt, &anotherInt); print("\(someInt), \(anotherInt)"); //交换String型 var someString = "hello"; var anotherString = "world"; swapTwoValues(&someString, &anotherString); print("\(someString), \(anotherString)");
3.类型参数
- 在上面的 例子中,占位类型 T 是类型参数的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 <T>)。
- 一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 swapTwoValues(_:_:) 函数中的参数 a 和 b),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 swapTwoValues(_:_:) 例子中,当函数第一次被调用时,T 被 Int 替换,第二次调用时,被 String 替换。)
- 你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
4.命名类型参数
- 在大多数情况下,类型参数具有一个描述性名字,例如 Dictionary<Key, Value> 中的 Key 和 Value,以及 Array<Element> 中的 Element,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 T、U、V。
5.泛型类型
//自定义泛型类型---栈 struct Stack<Element> { var items = [Element](); mutating func push(item: Element) { items.append(item); } mutating func pop() -> Element { return items.removeLast(); } } //String类型的Stack var stackOfStrings = Stack<String>(); stackOfStrings.push("uno"); stackOfStrings.push("dos"); stackOfStrings.pop(); stackOfStrings.pop(); //Int类型的Stack var stackOfInt = Stack<Int>(); stackOfInt.push(3); stackOfInt.pop();
6.扩展一个泛型类型
- 当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
struct Stack<Element> { var items = [Element](); mutating func push(item: Element) { items.append(item); } mutating func pop() -> Element { return items.removeLast(); } } extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1]; } } var stackOfStrings = Stack<String>(); stackOfStrings.push("uno"); if let topItem = stackOfStrings.topItem { print("The top item on the stack is \(topItem)."); //"The top item on the stack is uno.\n" }
7.类型约束
- 类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
类型约束语法:
//TODO:类型约束语法 func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // 这里是泛型函数的函数体部分 } //TODO:字典定义示例 public struct Dictionary<Key : Hashable, Value> : CollectionType, DictionaryLiteralConvertible { }
类型约束示例:
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? { for (index, value) in array.enumerate() { //如果不对T加类型约束Equatable,这里是不能直接用==进行比较的 if value == valueToFind { return index; } } return nil; } let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3); let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea");
8.关联类型
- 定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 associatedtype 关键字来指定关联类型。
protocol Container { associatedtype ItemType; var count: Int { get }; subscript(i: Int) -> ItemType { get }; mutating func append(item: ItemType); } struct Stack<Element>: Container { // Stack<Element> 的原始实现部分 var items = [Element](); mutating func push(item: Element) { items.append(item); } mutating func pop() -> Element { return items.removeLast(); } // Container 协议的实现部分 //这里占位类型参数 Element 被用作 append(_:) 方法的 item 参数和下标的返回类型。Swift 可以据此推断出 Element 的类型即是 ItemType 的类型。因此下面的这句代码可以不加,当然加上也没有问题 // typealias ItemType = Element; var count: Int { return items.count; } subscript(i: Int) -> Element { return items[i]; } mutating func append(item: Element) { self.push(item); } }
无善无恶心之体,
有善有恶意之动,
知善知恶是良知,
为善去恶是格物。