泛型函数
- 泛型可以将类型参数化,提高代码复用率,减少代码量
看个例子🌰,交换两个变量的值:定义一个函数,参数为inout
参数,内部元组实现交换两个外部传入的变量
func swapValue(_ a: inout Int, _ b: inout Int) {
(a, b) = (b, a)
}
var n1 = 10
var n2 = 20
swapValue(&n1, &n2)
print(n1, n2) // 20 10
上面的swapValue
函数的参数只支持Int
类型,用局限性,如果要支持其他类型的参数,就必须写多个函数,很麻烦,严重增加代码量,这时候就需要参数支持泛型,能传入任意类型的参数。
这里的T
代表一种不确定的类型:(这里的T
随便自定义,通常用T
,代表type
)
func swapValue<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var n1 = "abc"
var n2 = "123"
swapValue(&n1, &n2)
print(n1, n2) //123 abc
var n1 = true
var n2 = false
swapValue(&n1, &n2)
print(n1, n2) //false true
var n1 = 10.1
var n2 = 90.23
swapValue(&n1, &n2)
print(n1, n2) //90.23 10.1
其实swift
内部就已经提供了一个swap
交换的方法,我们点进去看下就可以知道我们自定义的swapValue
跟它的定义方法是一样的:
/// Exchanges the values of the two arguments.
///
/// The two arguments must not alias each other. To swap two elements of a
/// mutable collection, use the `swapAt(_:_:)` method of that collection
/// instead of this function.
///
/// - Parameters:
/// - a: The first value to swap.
/// - b: The second value to swap.
@inlinable public func swap<T>(_ a: inout T, _ b: inout T)
- 泛型函数赋值给变量
var n1 = 10
var n2 = 20
var fn: (inout Int, inout Int) -> () = swapValue
fn(&n1, &n2)
func test<T1, T2>(_ t1: T1, _ t2: T2) { }
var fn: (Int, Double) -> () = test
泛型类型
结构体、类、枚举也可以增加泛型定义
举个例子,利用数组实现一个栈,类Stack
后面加个<E>
,代表支持任意类型的元素操作
class Stack<E> {
var elements = [E]()
func push(_ element: E) { elements.append(element) }
func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
var intStack = Stack<Int>() //支持Int的栈
var stringStack = Stack<String>() //支持String的栈
var anyStack = Stack<Any>() //支持Any的栈
存在继承的写法:
class SubStack<E>: Stack<E> {
}
struct
中自定义需要加上mutating
struct Stack<E> {
var elements = [E]()
mutating func push(_ element: E) { elements.append(element) }
mutating func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
枚举中定义泛型
enum Score<T> {
case point(T)
case grade(String)
}
let score0 = Score.point(10)
let score1 = Score<Int>.point(10)
let score2 = Score<Double>.point(10.1)
let score3 = Score<Int>.grade("C")
关联类型 Associated Type
- 给协议中用到的类型定义一个占位名称
定义一个协议Stackable
,声明几个栈的常规操作,如果要实现一个栈,遵循Stackable
协议,内部自己去实现
protocol Stackable {
associatedtype Element //关联类型(可以理解泛型)
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
协议中实现泛型无法像类、结构体、枚举那样,只能用associatedtype
protocol Stackable<Element> { }
//报错:Protocols do not allow generic parameters; use associated types instead
- 协议中可以拥有多个关联类型
protocol Stackable {
associatedtype Element //关联类型
associatedtype Element2 //关联类型
associatedtype Element3 //关联类型
}
具体应用中的实现:
声明一个类StringStack
,遵循Stackable
协议,设置真实关联类型为String
类型。
class StringStack: Stackable {
//给关联类型设置真实类型
typealias Element = String
//...
}
因为编译器会自动处理,其实可以不用写明typealias Element = String
,只要实现协议方法的时候写明是String
就行,编译器会自动关联到需要的类型
class StringStack: Stackable {
//给关联类型设置真实类型
// typealias Element = String
var elements = [String]()
func push(_ element: String) { elements.append(element) }
func pop() -> String { elements.removeLast() }
func top() -> String { elements.last! }
func size() -> Int { elements.count }
}
想要更灵活的实现一个泛型的类,可以在定义class
的时候设置泛型:
class Stack<E>: Stackable {
// typealias Element = E
var elements = [E]()
func push(_ element: E) { elements.append(element) }
func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
类型约束
下面的代码中,对于泛型T
设置了一些约束,参数泛型T
必须是Person
类或者子类且遵循了Runnable
协议的。
protocol Runnable { }
class Person { }
func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
下面的代码中,协议里面的关联类型遵守Equatable
协议,那么遵守Stackable
协议的类的泛型也必须遵守Equatable
协议。
protocol Stackable {
associatedtype Element: Equatable
}
class Stack<E: Equatable>: Stackable {
typealias Element = E
}
下面的代码中,S1
、S2
必须遵守Stackable
协议,且S1.Element == S2.Element
,同时S1.Element
要遵守Hashable
协议。
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool
where S1.Element == S2.Element, S1.Element: Hashable {
return false
}
var s1 = Stack<Int>()
var s2 = Stack<Int>()
var s3 = Stack<String>()
equal(s1, s2) //编译正确
equal(s1, s3) //编译错误:Global function 'equal' requires the types 'Stack<Int>.Element' (aka 'Int') and 'Stack<String>.Element' (aka 'String') be equivalent
协议类型的注意点
定义Runnable
协议,定义类Person
、Car
遵守Runnable
协议。
可以看到变量r1
是Person
对象,r2
是Car
对象。
但是编译器并不知道r1
、r2
具体哪个是Person
,哪个是Car
,因为get
返回值统一是Runnable
类型
protocol Runnable { }
class Person: Runnable { }
class Car: Runnable { }
func get(_ type: Int) -> Runnable {
if type == 0 {
return Person()
}
return Car()
}
var r1 = get(0)
var r2 = get(1)
接下来给协议Runnable
引入关联类型associatedtype
:声明关联类型Speed
,有一个speed
只读的计算属性
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person: Runnable {
var speed: Double { 0.0 }
}
class Car: Runnable {
var speed: Int { 0 }
}
- 用泛型解决问题
func get<T: Runnable>(_ type: Int) -> T {
if type == 0 {
return Person() as! T
}
return Car() as! T
}
var r1: Person = get(0)
var r2: Car = get(1)
- 用不透明类型
some
解决问题
func get(_ type: Int) -> some Runnable {
return Car()
}
some
限制只能返回一种类型,因此编译器能确定最后返回的是什么类型,编译就能通过。
网友评论