1. Swift泛型的定义方法:
1) 和C++泛型概念一样,用法和C++也相似,同样也是使用一个类型占位符(代表一个通用类型,也是泛型的参数,用户随意取名,但要求按照标示符命名规则进行,因为其代表一个通用类型,因此和定义其它类型名的规范一样,最好是首字母大写的驼峰命名方式,一般取T);
2) 一个简单的泛型函数的例子:
func mySwap<T>(inout a: T, inout b: T) {
let t = a
a = b
b = t
}
var a = 1, b = 2
mySwap(&a, &b)
println(a) // 2
println(b) // 1
// Swift自己的库也含有swap泛型函数
swap(&a, &b) // 又换回来了
println(a) // 1
println(b) // 2
func myEqual<T: Comparable>(a: T, b: T) -> Bool {
return a == b
}
此例子中类型占位符为T,可以当做普通类型一样使用;
3) 和任何一种语言的泛型一样,都是动态推导类型的,只有当实际传参的时候才会根据参数类型生成相应版本的代码(即运行时动态加载函数代码),因此执行效率较低,但是程序灵活性非常强;
4) 多类型参数:如果有多个类型占位符,则声明的时候用逗号,隔开
func f<T, K>(a: T, b: K) -> K {
return b
}
println(f(12, "haha")) // haha
2. 泛型模板类以及扩展模板类:
1) 和C++模板类的概念一致,在类定义中可以含有类型占位符,用于表示一种通用类型;
!!泛型不仅支持函数,同时也支持结构体和枚举类型!
2) 定义类模板是需要在类名之后写上占位符,请看如下例子,模拟实现一个栈模板类:
struct Stack<T> {
var items = [T]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}
3) 扩展模板类时不需要重新写泛型参数列表,可以在扩展体中直接使用定义类时的泛型,非常方便:接上例代码
extension Stack {
subscript(index: Int) -> T? {
if index < 0 || index + 1 > countElements(items) {
return nil
}
return items[index]
}
}
3. 泛型约束:
1) 顾名思义,就是要求那个类型占位符所代表的泛型遵守某些规矩,比如必须要遵守某些协议,或者必须由什么类继承而来等;
2) 实际上只能约束泛型遵守什么协议或者泛型继承自那个类,比如对于比较两个泛型是否内容相等,就不能直接使用普通的泛型,必须使用能遵守Equatable协议的两个泛型才能比较,该协议规定类型必须实现==和!=两种操作符的实现,可以想象,如果不遵守该协议,直接在泛型函数体中使用==操作符比较两个泛型是否相等,如果该泛型传进来的是一个用户自定义的类并且该类没有实现==操作符,那该如何让该泛型函数进行比较呢?请看以下的例子:
func equals<T: Equatable>(a: T, b: T) -> Bool {
return a == b
}
如果要求遵守多个协议则用逗号隔开,就和继承类、遵守协议的语法一模一样,比如:
func equals<T: Equatable, Hashable>(a: T, b: T) -> Bool { // 既遵守Equatable协议也遵守Hashable协议
return a == b
}
3) 再来看一个例子,该例子是在一个泛型数组中查找某个元素并返回下标:
func findItemIndexFrom<T: Equatable>(arr: [T], byValue val: T) -> Int? {
for (index, value) in enumerate(arr) {
if value == val {
return index
}
}
return nil
}
4. 关联类型——实现“泛型协议”:
1) Swift不运行定义泛型协议(至少语法上不允许像定义泛型类型那样定义泛型协议),但是有时有这方面的需要,比如在一个协议中指定一种泛型,然后协议中规定了很多方法或属性都需要用到该泛型,但一个类遵守该协议时再根据具体需要规定该泛型的具体类型,同时并使用该具体类型来实现协议中规定的方法和属性;
2) 答案时肯定的,Swift提供关联类型这种形式来解决以上问题,即可以先用typealias定义一个泛型(即不对该泛型赋值,值定义其名字),然后在规定要实现的属性或方法中使用该泛型,最后是当一个类型遵守该协议时再用typealias对该泛型进行赋值,使其成为某个具体的类型,接着再实现规定的属性和方法时用具体的类型替换该泛型即可:
protocol A {
typealias ItemType // 先指定一个泛型
// 规定要实现的内容中暂时用泛型替代
mutating func push(item: ItemType)
mutating func pop() -> ItemType
}
struct Stack<T>: A {
var items = [T]()
// 遵守协议时再将泛型和某种具体的类型“相关联”,而该某种具体的类型可以是模板类的类型占位符!
// 可见Swift超级强大
typealias ItemType = T // 同样是可写可不写,编译器能自动推断
mutating func push(item: T) { // 实现时用具体类型代替关联类型
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}
struct IntStack: A { // 一个Int的版本
var items = [Int]()
typealias ItemType = Int // 词句可写可不写,编译器都能自动推断
mutating func push(item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
5. 用where语句对关联类型进行约束:
1) 和SQL的where约束类似,该约束发生在指定某个类型遵守某个协议(协议带有关联类型)时使用;
2) 具体语法如下:
protocol ProtocolStack {
typealias ItemType
mutating func push(item: ItemType)
mutating func pop() -> ItemType
var count: Int { get }
subscript(index: Int) -> ItemType { get }
}
struct Stack<T>: ProtocolStack {
var items = [T]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
var count: Int { return countElements(items) }
subscript(index: Int) -> T {
return items[index]
}
}
func isAllItemMatched<
STK1: ProtocolStack, STK2: ProtocolStack
where STK1.ItemType == STK2.ItemType, STK1.ItemType: Equatable
>(stk1: STK1, stk2: STK2) -> Bool {
// 该函数用于比较两个栈中的内容是否完全相等
// 因此两个类型都必须遵守ProtocolStack协议
// 并且栈中元素必须遵守可比较协议Equatable
// 元素进行比较的前提是两种栈中元素的类型必须相同,这也就是要求两种关联类型必须相同
if ( stk1.count != stk2.count ) {
return false
}
for i in 0..<stk1.count {
if stk1[i] != stk2[i] {
return false
}
}
return true
}
3) 对以上成果进行使用:泛型类型定义方法和C++类似,如下
var stk1 = Stack<Int>()
var stk2 = Stack<Int>()
for i in 0..<10 {
stk1.push(i)
stk2.push(i)
}
println(isAllItemMatched(stk1, stk2)) // true