目录
- 在协议中如何使用泛型
- 关联类型的关键字 :associatedtype
- 协议中想要使用泛型不能像在class类中那样使用
- 在协议中使用关联类型代替泛型
- 在带泛型的class中,泛型类型填充关联类型
- 泛型类型被基础类型替换
- 协议中关联类型的注意事项
- 关联类型的协议不能作为返回值,函数形参
- 下面是正常的可以编译过的代码,协议中没有关联类型
- 下面是编译错误的代码
- 不能作为返回类型
- 不能作为函数形参
- 解决让class遵循带关联类型的协议,并且能当做形参和返回值的方法
- 解决方法1:让泛型遵循协议,然后让泛型当做形参或返回值,代码如下
- 解决方法2:不明确类型
在协议中如何使用泛型
关联类型的关键字 :associatedtype
在协议中的格式如下:
associatedtype 类型变量,例如下面格式:
associatedtype E
associatedtype Base
协议中想要使用泛型不能像在class类中那样使用
protocol Stackble <Element>{ //这里编译会报错协议中不允许使用泛型参数,请使用关联类型代替泛型,Protocols do not allow generic parameters; use associated types instead
mutating func push(_ element:Element)
mutating func pop()->Element
func top() ->Element
func size() ->Int
}
在协议中使用关联类型代替泛型
- 因为协议不允许使用泛型参数,想要协议使用泛型,请使用关联类型代替,正确的写法如下:
- 只是把protocol<泛型类型> 改成了在{添加 associatedtype 泛型类型}
protocol Stackble { //定义一个栈的协议
associatedtype Element// 在协议中用来占位的类型是关联类型,声明一个关联类型Element
mutating func push(_ element:Element)
mutating func pop()->Element
func top() ->Element
func size() ->Int
}
在带泛型的class中,泛型类型填充关联类型
关联类型,是在协议中占位用的,class类中的泛型会代替关联类型
在class类中实现协议的代码如下: 可以看到class类中的泛型E,在重写push的时候,把类型换成了E,这样E就等于协议中的Element,这样,协议中其他方法 pop top ,这些事Element类型的,在class中都变成了泛型E
class Stack <E>: Stackble {
var elements = [E]() //用关联类型
func push(_ element:E){ //这里因为实现push 的时候给element输入了E,所以就相当于给Element 起了别名E
//等于typealias E = Element
elements.append(element)
}
func pop()->E{
elements.removeLast()
}
func top() ->E{
elements.last!
}
func size() ->Int{
elements.count
}
}
调用代码如下
var s1 = Stack<Int>()
s1.push(1)
s1.push(2)
s1.push(3)
print("s1.top()" ,s1.top())
运行打印结果:
s1.top() 3
泛型类型被基础类型替换
下面代码Stackble协议中的push方法的参数在协议中是Element类型,在class被重写的时候,push 的参数被重写成String,所以 Stackble协议中的其他方法,是Element类型类型的,都自动变成String
class StringStack:Stackble{
var elements = [String]()
func push(_ element:String){ //直接把关联类型用String指定
elements.append(element)
}
func pop()->String{
elements.removeLast()
}
func top() ->String{
elements.last!
}
func size() ->Int{
elements.count
}
}
调用类的代码:
var ss1 = StringStack()
ss1.push("a1")
ss1.push("a2")
ss1.push("a3")
print("ss1.top()",ss1.top())
print("ss1.size()",ss1.size())
执行打印结果:
ss1.top() a3
ss1.size() 3
协议中关联类型的注意事项
关联类型的协议不能作为返回值,函数形参
下面是正常的可以编译过的代码,协议中没有关联类型
protocol Runnable {} //普通的协议,没有关联类型associatedtype
class Person:Runnable{}
class Car:Runnable{}
func get(_ type:Int) -> Runnable {//如果传入0,就创建Person对象,否则创建Car对象
if(0 == type ){
return Person()
}
return Car()
}
var r1 = get(0)//var r1: Runnable
var r2 = get(1)//因为Runnable协议中没有关联类型,所有类型都明确,所以可以正常返回
print("r1=",r1)
print("r2=",r2)
执行打印结果,分别打印Person和Car:
r1= _7协议作为类型中有associatedtype的注意点.Person
r2= _7协议作为类型中有associatedtype的注意点.Car
代码中
下面是编译错误的代码
不能作为返回类型
protocol Runnable {
associatedtype Speed
var speed : Speed {get}
} //协议中有关联类型associatedtype,类型不确定
class Person:Runnable{
var speed: Double = 0.0
}
class Car:Runnable{
var speed: Double = 0.0
}
//协议Runnable里面有关联类型,类型不确定,所以不能当做定义函数的参数类型和返回值类型使用
func get(_ type:Int) -> Runnable {//编译错误,协议有不确定关联类型,不能作为函数返回值
if(0 == type ){
return Person()
}
return Car()
}
不能作为函数形参
fun get (run:Runnable ) {} //编译错误,协议Runnable里面有关联类型,类型不确定,所以不能当做定义函数的形参
解决让class遵循带关联类型的协议,并且能当做形参和返回值的方法
解决方法1:让泛型遵循协议,然后让泛型当做形参或返回值,代码如下
func get<T:Runnable>(_ type:Int)->T{ //让泛型类型T遵守协议,然后返回T
if 0 == type{
// let result = Person() as T //编译错误,系统认为 Person() 创建的结果 强转成T 可能失败,所以要用as!强转,因为可能失败,可能返回nil,所以是可选类型,要用as !
let result = Person() as! T
return result
}
return Car() as! T
}
解决方法2:不明确类型
some让协议的关联类型变成透明的, 在协议前面标记上 some 后,返回值的类型对编译器就变成透明的了。在这个值使用的时候编译器可以根据返回值进行类型推断得到具体类型。如果不加some 编译报错,会认为返回的是个关联类型,是不确定的类型
@available(OSX 10.15.0, *)//要求系统超过10.15,编译提示自动添加
func get2(_ type:Int )-> some Runnable{ //some让协议的关联类型变成透明的, 在协议前面标记上 some 后,返回值的类型对编译器就变成透明的了。在这个值使用的时候编译器可以根据返回值进行类型推断得到具体类型。如果不加some 编译报错,会认为返回的是个关联类型,是不确定的类型
return Car() //some只能返回一种类型
}
下面代码是错误的,因为some不能返回2种类型:
func get3(_ type:Int )-> some Runnable{ //编译错误,some限制的类型不能返回2种类型
if 0 == type{
return Person()
}
return Car() //some只能返回一种类型
}