泛型编程的目的是表达算法或者数据结构所要求的核心接口; 使用泛型,你可以写出重用的函数和数据结构, 只要它们满足你所定义的约束,它就可以适用于各种类型
可以使用泛型进行更好的代码设计
- 提取共用功能的代码
- 创建泛型数据类型
关于泛型和重载的探索
- 理解重载 : 拥有同样的函数名, 但是参数和返回类型不同的多个函数方法, 互相称为重载;
- 理解泛型 : 泛型是程序设计语言的一种特性, 非常灵活, 允许程序在函数,枚举,结构体,类中定义类型形参,使类型参数化以达到代码复用提高软件开发工作效率的一种方式;
用简单的例子理解泛型 和 重载
- 重载的代码示例
//交换两个参数的值
//Int类型的交换
func swapTwoValues(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
//String 类型的交换
func swapTwoValues(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
//调用
var numb1 = 100
var numb2 = 200
print("交换前数据: \(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")
//打印的值
交换前数据: 100 和 200
交换后数据: 200 和 100
//调用
var str1 = "Swift"
var str2 = "Java"
print("交换前数据: \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交换后数据: \(str1) 和 \(str2)")
交换前数据: Swift 和 Java
交换后数据: Java 和 Swift
复制代码
也许你还想支持更多的数据类型,比如Double, Float, Bool等等, 你需要针对每一个数据类型都实现一次swapTwoValues方法, 这被称为重载;
- 但有了泛型,你将有了更聪明的处理方法
//泛型的代码示例
//交换两个参数的值
func swapTwoValues<T>(_ a: inout T, _ b: inout T){
let temporaryA = a
a = b
b = temporaryA
}
//调用
var double1 = 20.0
var double2 = 30.0
print("交换前数据: \(double1) 和 \(double2)")
swapTwoValues(&double1, &double2)
print("交换后数据: \(double1) 和 \(double2)")
//打印
交换前数据: 20.0 和 30.0
交换后数据: 30.0 和 20.0
//调用
var str1 = "Swift"
var str2 = "Java"
print("交换前数据: \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交换后数据: \(str1) 和 \(str2)")
//打印
交换前数据: Swift 和 Java
交换后数据: Java 和 Swift
复制代码
相对于重载,泛型的优势得到了充分的发挥;
但我们对泛型与重载的知识讨论还远远没有结束
- Swift有一套复杂的重载函数使用规则优先级,会根据函数是否是泛型和传入的参数是怎样的以及返回值的类型来确定会调用哪个函数;
//代码示例:
class OverloadObject {
//重载的函数
func raise(_ base : Double, to exponent: Double) -> Double {
return pow(base, exponent)
}
func raise(_ base : Float, to exponent: Float) -> Float {
print("调用了 Float的方法")
return powf(base, exponent)
}
}
//当 raise 函数被调用时,编译器会根据参数和/或返回值的类型为我们选择合适的重载
let overload = OverloadObject()
let double = overload.raise(2.0, to: 3.0)
type(of: double)//返回的是Double 类型
// 此时调用的是func raise(_ base : Double, to exponent: Double) -> Double
//但只要我们明确的声明了返回值的类型为Float, 它就会调用func raise(_ base : Float, to exponent: Float) -> Float
let float1 : Float = overload.raise(2.0, to: 3.0)
type(of: float1) //返回的是Float类型
//此时调用的是func raise(_ base : Float, to exponent: Float) -> Float
复制代码
- 关于Swift中的重载, 非通用函数会优先于通用函数被调用;
//代码示例解释:
//通用的泛型函数
func log<View:UIView>(_ view : View){
print("it is a \(type(of: view)), frame : \(view.frame)")
}
//非通用的函数
func log(_ label : UILabel){
let text = label.text ?? "empty text"
print("it is a \(type(of: label)), text : \(text)")
}
//调用
let label = UILabel(frame: CGRect(x: 20, y: 20, width: 200, height: 32))
label.text = "Password"
//当传入的是 label时, 会根据参数调用非通用的 func log(_ label : UILabel) 函数
log(label)
//打印的数据
it is a UILabel, text : Password
//当传入的是 button时, 会调用 通用 func log<View:UIView>(_ view : View) 函数
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
log(button)
//打印的数据
It is a UIButton, frame : (0.0, 0.0, 100.0, 50.0)
//通过例子,我们充分证明了当函数被调用时,非通用函数会优先于通用函数被调用
复制代码
- 重载的使用是在编译期间静态决定的. 也就是说,编译器会依据变量的静态类型来决定要调用哪一个重载,而不是在运行时根据值的动态类型来决定
- 对于面向对象来说,接口是显式的,是基于类型定义和方法签名的,多态是发生在运行时的;
- 而对于泛型编程,接口则是隐式的,是为了支持算法实现的,多态则是发生在编译期的。
//代码案例解释:
//通用的泛型函数
func log<View:UIView>(_ view : View){
print("it is a \(type(of: view)), frame : \(view.frame)")
}
//非通用的函数
func log(_ label : UILabel){
let text = label.text ?? "empty text"
print("it is a \(type(of: label)), text : \(text)")
}
let label = UILabel(frame: CGRect(x: 20, y: 20, width: 200, height: 32))
label.text = "Password"
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
let views = [label, button]
for view in views {
log(view)
}
//log 调用的结果:
it is a UILabel, frame : (20.0, 20.0, 200.0, 32.0)
it is a UIButton, frame : (0.0, 0.0, 100.0, 50.0)
//从打印的日志可以看到, 即便是label, 也是调用了func log<View:UIView>(_ view : View) 函数
//而不是动态的去匹配func log(_ label : UILabel)
复制代码
- 在Swift中, 可以通过重载函数的方式自定义运算符; 但是对于重载的运算符, 类型检查器会去使用非泛型的重载方法, 而不使用泛型函数的重载
//代码例子: 自定义一个求幂的运算符 **
//Swift 源码中对运算符的定义
precedencegroup ExponentiationPrecedence {
associativity: left
higherThan: MultiplicationPrecedence
}
//告诉Swift, `**` 是一个 `infix` 运算符,也就是需要两个操作数,运算符在两个数的中间
infix operator **: ExponentiationPrecedence
func **(lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}
func **(lhs: Float, rhs: Float) -> Float {
return powf(lhs, rhs)
}
//现在 ** 可以作为运算符来使用了
2.0 ** 3.0
复制代码
现在我想实现一个支持 任意Int类型的 求幂运算, 可以通过一个泛型函数来实现
//BinaryInteger 协议 提供所有integer数据类型的基础方法
func **<I : BinaryInteger>(lhs:I, rhs:I) ->I{
let result = Double(Int64(lhs)) ** Double(Int64(rhs));
return I(result);
}
//调用
2 ** 3 //此时你会发现编译器会报错,发生了语义上的冲突
//发生歧义是因为编译器忽略了整数的泛型重载,因此它无法确定是去调用 Double 的重载还是 Float 的
//重载,因为两者对于整数字面量输入来说,是相同优先级的可选项 (Swift 编译器会将整数字面量在需要时
//自动向上转换为 Double 或者 Float),所以编译器报错说存在歧义。
//这也是我想讨论的: <!--类型检查器会去使用非泛型的重载方法, 而不使用泛型函数的重载-->
//这个时候 只有你明确的提供返回值类型 或者明确的声明一个参数的类型, 就可以了
let result : Int = 2 ** 3
//这次使用, 就会正确的调用泛型重载函数 func **<I : BinaryInteger>(lhs:I, rhs:I) ->I 了;
复制代码
到这里,你肯定可以更好的理解关于泛型与重载的相关知识了;