本文将泛型的常见设置方式进行讲解,以及协议使用关联类型的问题和解决
主要内容:
- 泛型认识
- 关联类型
- 类型约束
- 泛型的实际使用
- 不透明类型
1. 泛型认识
泛型基本上和Java是一样的,可以用在函数,也可以用在类型上,泛型通过类型参数化,共用类型,提高代码复用率,泛型实现的本质是将类型作为参数传入到函数中。需要注意是否显式写泛型取决于是否已经明确类型
用在函数
代码:
//用在函数
//定义一个交换函数,通过元组交换的,定义T为泛型
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
//分别交换int、float、struct类型
//传参时就可以决定类型,所以这里的swapValues后面不需要加<T>
var i1 = 10
var i2 = 20
swapValues(&i1, &i2)
var d1 = 10.0
var d2 = 20.0
swapValues(&d1, &d2)
struct Date {
var year = 0, month = 0, day = 0
}
var dd1 = Date(year: 2011, month: 9, day: 10)
var dd2 = Date(year: 2012, month: 10, day: 11)
swapValues(&dd1, &dd2)
print(dd1)
print(dd2)
//将函数赋给一个变量
var n1 = 10
var n2 = 20
let fn: (inout Int, inout Int) -> () = swapValues
fn(&n1, &n2)
print(n1,n2)//20 10
说明:
1、泛型的含义以及定义和Java一样
2、但是这里调用时不需要传入泛型,比如不能swapValues< Int >,因为传入参数时就可以确定类型
3、赋值给变量需要设置类型,因为此时的swapValues传入的参数就是Int,所以这里需要设置类型
用在类型
代码:
//用在类型
//自定义一个栈,实现简单的出栈入栈,使用elements数组来存储元素
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>()
intStack.push(1)
print(intStack.size())
intStack.pop()
print(intStack.size())
var stringStack = Stack<String>()
说明:
- 需要注意的是这里可以加上< Int >约定类型,因为没有传入参数无法预知类型
- 如果直接调用Init传入参数来创建对象时,就不需要加上<>了
用在枚举
代码:
//用在枚举
enum Score<T> {
case point(T)
case grade(String)
}
//这里虽然使用了grade,没使用point,但是只要创建这个枚举,就应该传入泛型类型
let score = Score<Int>.grade("A")
说明:
- 这里虽然没有用用到point,但是也要加上泛型,因为枚举要确定内存结构
2. 关联类型
协议通过关联类型来实现泛型,关联类型可以给协议中用到的类型定义一个占位名称,使用关键字associatedtype来定义。
代码:
/*
2、关联类型
其实就是协议的泛型
*/
//创建一个泛型,定义了一个类型Element,该类型遵守Equatable协议
protocol Stackable {
associatedtype Element : Equatable
}
func test2() {
//创建一个类,泛型类型必须遵守该协议,并且将该类型赋值给Element
class Stack<E : Equatable> : Stackable {
typealias Element = E
}
}
说明:
1、泛型可以增加约束,这里是遵守Equatable协议的元素作为泛型
2、因此在类中想要给其赋值,这个E也必须遵守Equatable协议
3、通过typealias Element = E 把泛型设置上
3. 类型约束
代码:
/*
3、类型约束
*/
//通过where可以增加类型的约束
/*
约束如下:
1. S1、S2 都是泛型,
2. 泛型都遵守协议Stackable
3. S1和S2的关联类型要相等,这个Element是在类Stack中赋值的,他们要赋值相同
4. S1的关联类型必须遵守Hashable协议
*/
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
return false
}
var stack1 = Stack<Int>()
var stack2 = Stack<Int>()
var stack3 = Stack<String>()
print(equal(stack1, _s2: stack2))
print(equal(stack1, _s2: stack3))
说明:
1、S1、S2 都是泛型,且泛型都遵守协议Stackable
2、S1和S2的Element要相等,这个Element是在类Stack中赋值的,他们要赋值相同
3、S1的关联类型必须遵守Hashable协议
4. 泛型的实际使用
4.1 代码优化
代码:
/*
4、应用:代码优化
*/
protocol Runnable {}
class Person : Runnable {}
class Car : Runnable {}
//这里返回值只要写上Runnable,那么返回的只要是遵守该协议的就都可以
//否则每个类型都写一个方法,代码冗余
func get(_ type: Int) -> Runnable {
if type == 0 {
return Person()
}
return Car()
}
var r1 = get(0)
var r2 = get(1)
说明:
- 一个类要返回多种类型的方法,就可以使用
4.2 问题
协议中有关联类型,
代码:
说明:
- 在协议中有关联类型,如果在使用该属性时么有设置该关联类型,就会报错
4.3 解决
4.3.1 使用泛型
代码:
/*
4、应用:代码优化
*/
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person : Runnable {
var speed: Double { 0.0 }
}
class Car : Runnable {
var speed: Int { 0 }
}
//这里返回值只要写上Runnable,那么返回的只要是遵守该协议的就都可以
//否则每个类型都写一个方法,代码冗余
/*
增加泛型
1、方法中传入泛型,该类型必须遵守Runnable协议
2、最后返回这个类型
3、当然在返回的时候就需要判断一下是否遵守该协议
*/
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)
说明:
1、这里可以明确泛型的类型,这样就可以解决
2、明确返回的类型是Person或Car,这样泛型的Speed就是明确的
3、只不过这里在返回时需要强制转换类型为T
4.3.2 使用不透明类型
代码:
/*
5、不透明类型
*/
//这里可以使用some来
func get(_ type: Int) -> some Runnable {
return Person()
}
var r1 = get(0)
var r2 = get(1)
说明:
1、不透明在于这里外界只知道返回一个遵守runnable协议的类,而不知道具体的类
2、得到的类型所能看到的信息是Runnable,Person类中的信息看不到
3、只能返回一种类型
4、some除了用在返回值类型上,一般还可以用在属性类型上
网友评论