Swift-31-泛型和类型操作

泛型

Swift泛型(generics) 让我们写出的类型和函数可以使用对于我们或编译器都未知的类型。 很多内建类型(包括可空类型、数组和字典)都是用泛型实现的,比如数组和一些集合就是用泛型方式来实现的。

一种运行时进行类型检查的技术,效率高但是不安全。在swift中泛型可用于结构体、类以及函数和方法

定义可空类型的泛型变量

swift 复制代码
let x: Optional<Int> = 3

print(x) //~~ 3

上述代码实际上用到了系统提供的协议,具体的在后续详细讲解,这里只了解其用法就可以了

swift 复制代码
enum Optional<Wrapped> {
    case None
    case Some(Wrapped)
}

定义泛型类型

语法:class/struct Name<Type>,上述尖括号中的Type是一个占位符,也可换成其它名称,比如T,在实例化时会换成实际的值。

比如一个简单的堆栈实现

swift 复制代码
//Element是一个占位符,也可换成其它名称
struct Stack<T>: Sequence { 
    var items = [T]()

    mutating func push(_ newItem: T) {
        items.append(newItem)
    }
    
     mutating func pop() -> T? {
        guard !items.isEmpty else {
            return nil
        }
        return items.removeLast()
    }
}

//~~~使用,在代码运行时,泛型T会换成Int
var stack = Stack<Int>()    

定义泛型函数和方法

函数和类的方法的返回值和参数也可以用泛型来代码,比如下拉代码声明了一个泛型函数。

swift 复制代码
    func myMap<T,U> ( _ items:[T], _ f:(T)->(U) )  -> [U]{
        var result = [U]()
        for item in items{
            result.append( f(item) )
        }
        return result
    }

方法测试

swift 复制代码
let string = ["one", "two", "three"]
let stringLen = myMap(string){$0.count} //第一个cod为一个函数
print(stringLen) //~~ [3,3,5]

给泛型占位符设置约束条件

泛型的比较,必须要把泛型标识符声明为系统提供的Equatable类型。

swift 复制代码
func checkIfEqual<T: Equatable>(_ first: T, _ second: T) -> Bool {
    return first == second
}
print(checkIfEqual(1, 1)) //~~true
print(checkIfEqual("a string", "a string")) //~~true
print(checkIfEqual("a string", "a different string")) //~~false

多个约束符的例子,下例表示用CustomStringConvertible保证了first和second都有返回字符串的属性description。

swift 复制代码
func checkIfDescriptionsMatch<T: CustomStringConvertible, U: CustomStringConvertible>(
        _ first: T, _ second: U) -> Bool {
    return first.description == second.description
}

print(checkIfDescriptionsMatch(Int(1), UInt(1)))
print(checkIfDescriptionsMatch(1, 1.0))
print(checkIfDescriptionsMatch(Float(1.0), Double(1.0)))

泛型与协议

协议是不可以直接使用泛型的,如果想在协议中使用泛型,可以使用一个叫"关联类型"的特性。用到关键字 associatedtype来修饰协议属性,比如系统提供的IteratorProtocol协议就是如下定义的:

swift 复制代码
protocol IteratorProtocol {
    associatedtype Element 
    mutating func next() -> Element?
}

上述associatedtype Element表示符合这个协议的类型必须提供具体类型做为Element类型。符合这个协议的类型应该在其定义为内部为Element提供typealias定义,那么就可以按如下方式使用了:

swift 复制代码
//用 StackIterator 把Stack封装起来。
struct StackIterator<T>: IteratorProtocol {
    typealias Element = T
    
    var stack: Stack<T>
    
    mutating func next() -> Element? {
        return stack.pop()
    }
}

这段代码可以借助Swift类型推断功能,简写为如下形式:

swift 复制代码
struct StackIterator<T>: IteratorProtocol {
    var stack: Stack<T>

    mutating func next() -> T? {
        return stack.pop()
    }
}

使用

swift 复制代码
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
var myStackIterator = StackIterator(stack: myStack)
while let value = myStackIterator.next() {
    print("got \(value)")
}

where子语句

用占位类型s把pushAll(_:)变成泛型方法,它是符合Sequence协议的类型。S的约束保证我们可以用for-in语法循环遍历之。不过,这还不够。为了把从sequence中取出的数据项推入栈,需要确保从序列类型中来的数据项类型和栈元素的类型匹配。也就是说,还需要一个约束让S所产生元素的类型是Element。

swift 复制代码
    //where语句相当于一个过滤器
    mutating func pushAll<S: Sequence>(_ sequence: S) 
                                       where S.Iterator.Element == Element {
        for item in sequence {
            self.push(item)
        }
    }

附:Stack 示例实现

swift 复制代码
struct Stack<Element>: Sequence {
    var items = [Element]()

    mutating func push(_ newItem: Element) {
        items.append(newItem)
    }

    mutating func pop() -> Element? {
        guard !items.isEmpty else {
            return nil
        }
        return items.removeLast()
    }

    func map<U>(_ f: (Element) -> U) -> Stack<U> {
        var mappedItems = [U]()
        for item in items {
            mappedItems.append(f(item))
        }
        return Stack<U>(items: mappedItems)
    }

    // Sequence 协议的方法
    func makeIterator() -> StackIterator<Element> {
        return StackIterator(stack: self)
    }

    mutating func pushAll<S: Sequence>(_ sequence: S) where S.Iterator.Element == Element {
        for item in sequence {
            self.push(item)
        }
    }
}

类型操作

值的比较

类型的比较在很多场景下都有需求,在Swift中可通过实现Equatable和Comparable这两个协议来实现。

实现Equatable协议

swift 复制代码
struct Point: Equatable {
    let x: Int
    let y: Int
    static func == (lhs: Point, rhs: Point) -> Bool {
        return (lhs.x == rhs.x) && (lhs.y == rhs.y)
    }
}
let a = Point(x: 3, y: 4)
let b = Point(x: 3, y: 4)
let abEqual = (a == b) //~~ true
let noAbEqual = (a != b) //~~ false

上述代码中==(中缀运算符)被声明为了static方法,事实上==是定义在全局范围内的。

实现Comparable协议

Comparable会提供更多的功能,因为Comparable继承了Equatable。

swift 复制代码
//自定义的Point结构体实现Comparable
struct Point: Comparable { //因为继承的原因,所以这块不需要写成Equatable,Comparable 
    let x: Int
    let y: Int
    
    static func ==(lhs: Point, rhs: Point) -> Bool {
        return (lhs.x == rhs.x) && (lhs.y == rhs.y)
    }
    
    static func <(lhs: Point, rhs: Point) -> Bool {
        return (lhs.x < rhs.x) && (lhs.y < rhs.y)
    }
}

let a = Point(x: 3, y: 4)
let b = Point(x: 3, y: 4)

let abEqual = (a == b) //true
let abNotEqual = (a != b) //false

let c = Point(x: 2, y: 6) //false
let d = Point(x: 3, y: 7) //false

let cdEqual = (c == b) //false
let cLessThanD = (c < d) //true

let cLessThanEqualD = (c <= d) //true
let cGreaterThanD = (c > d) //false
let cGreaterThanEqualD = (c >= d) //false

自定义运算符

Swift允许开发者创建自定义运算符。这个特性意味着我们可以创建自己的运算符来表示两个Person的实例结婚了。自定义运算符不太建议使用,因为它也只限于数学运算范围内,正常情况下使用系统提供的就够了。

定义自定义类

swift 复制代码
class Person: Equatable {
    var name: String
    var age: Int
    weak var spouse: Person?
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    func marry(_ spouse: Person) {
        self.spouse = spouse
        spouse.spouse = self
    }
}

添加自定义运算符

在Person类声明的外面添加以下代码,以添加自定义运算符。

swift 复制代码
//声明一个新运算符
infix operator +++
func +++(lhs: Person, rhs: Person) {
    lhs.spouse = rhs
    rhs.spouse = lhs
}

添加自定义运算符到默认组

swift 复制代码
precedencegroup Marriage {
    associativity: none  //这是一个运算优先级定义
}

如果没有上述代码,则因为swift内部对新添加的运算符默认添加到swift内部默认的组为DefaultPrecedence。

使用自定义运算符

swift 复制代码
let drew = Person(name: "Drew", age: 33)
let matt = Person(name: "Matt", age: 32)
相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
2501_916008893 小时前
iOS应用开发工具全面解析:如何选择与优化开发效率
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
a4493153623 小时前
iPhone Face ID 故障的分层诊断——原深感系统与主板加密配对机制
macos·ios·电脑·cocoa·iphone
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
白玉cfc4 小时前
【iOS】内存五大分区
macos·ios·cocoa
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf