29、Swift 中的关联类型、运算符重载与下标

Swift 中的关联类型、运算符重载与下标

关联类型概述

关联类型是在协议中使用的泛型类型。与其他泛型类型不同,协议不支持泛型参数子句,因此关联类型使用 typealias 关键字在协议内部定义(例如 typealias T )。实际使用的类型由符合该协议的类、结构体或枚举决定。关联类型用于开发库类型、方法和函数,如 Swift 标准库中的那些。关于关联类型的示例,可参考 此处

泛型回顾

在之前的学习中,我们了解了如何定义泛型函数、方法和类型。为函数和类型添加泛型参数子句,并在泛型参数子句中指定类型参数。Swift 会根据函数调用中的参数值来确定泛型函数的类型参数。同时,我们还学习了如何使用类型约束来限制类型参数的类型参数。我们定义了泛型数据结构类型,并使用显式类型参数来定义泛型类型的特定对象。

运算符重载简介

在操作对象时,调用方法和访问属性的表示法对于某些操作可能会很繁琐,例如数学类型的算术运算。Swift 允许使用运算符重载,使运算符能够与类和结构体对象一起工作。实际上,我们在之前的学习中已经使用了 Swift 标准库中内置的重载运算符。例如,Swift 重载了加法运算符 + ,根据上下文不同执行不同的操作:用于 Int 操作数时,将两个 Int 值相加并返回它们的和;用于两个 String 操作数时,将两个字符串的内容连接起来并返回一个新的字符串。

除了赋值运算符 = 和三元条件运算符 ?: 之外,你可以重载任何内置运算符。重载运算符执行的任务也可以通过显式函数调用来完成,但运算符表示法通常更自然和简洁。此外,你还可以创建全新的运算符。

String 类型的运算符和方法

以下是一个展示 String 类型各种重载运算符和方法的示例代码:

// Fig. 12.1: SwiftStrings.playground
// String operators and other String methods

// custom prefix unary ! operator returns String isEmpty property’s value
prefix func !(s: String) -> Bool {
    return s.isEmpty              
}                                 

// create Strings for testing String operators and methods
var s1 = "happy"
let s2 = " birthday"
var s3 = ""

// test overloaded String comparative operators
println("s1 is \"\(s1)\"; s2 is \"\(s2)\"; s3 is \"\(s3)\"")

println("\nTHE RESULTS OF COMPARING S1 AND S2")
println("s2 == s1 is \(s2 == s1)")
println("s2 != s1 is \(s2 != s1)")
println("s2 >  s1 is \(s2 > s1)")
println("s2 <  s1 is \(s2 < s1)")
println("s2 >= s1 is \(s2 >= s1)")
println("s2 <= s1 is \(s2 <= s1)")

// test String overloaded ! operator
println("\nTESTING CUSTOM OVERLOADED ! OPERATOR")

if !s3 { // uses custom ! prefix unary operator
    println("s3 is empty; assigning s1 to s3")
    s3 = s1
    println("s3 is \"\(s3)\"")
}

// test overloaded String concatenation operators
println("\nTESTING STRING CONCATENATION")
println("The result of s1 + s2 is the new String \"\(s1 + s2)\"")
s1 += s2 // no return value, so can’t insert in a String via interpolation
println("After s1 += s2, s1 is \"\(s1)\"")

// using String’s [] substring operator
println("\nTESTING [] FOR SUBSTRINGS")
let index = advance(s1.startIndex, 5)       
let substring1 = s1[s1.startIndex ..< index]
println("s1[s1.startIndex ..< index] is \"\(substring1)\"")
let substring2 = s1[index ..< s1.endIndex]
println("s1[index ..< s1.endIndex] is \"\(substring2)\"")

// test various other String methods
println("\nTESTING OTHER STRING METHODS")
var result = s1.hasPrefix("hap") // check whether s1 starts with “hap”
println("s1.hasPrefix(\"hap\"): \(result)")
result = s1.hasSuffix("day") // check whether s1 ends with “day”
println("s1.hasSuffix(\"day\"): \(result)")

s1.removeAtIndex(index) // removes space at position index
println("After s1.removeAtIndex(index), s1 is \"\(s1)\"")

s1.insert(" ", atIndex: index) // insert a space at position index
println("After s1.insert(\" \", atIndex: index), s1 is \"\(s1)\"")

s1.append(Character("!")) // append ! to the end of s1
println("After s1.append(Character(\"!\")), s1 is \"\(s1)\"")

s1.removeAll() // remove all characters in s1
println("After s1.removeAll(), s1 is \"\(s1)\"")

let joinResult = "*".join(["1", "2", "3"]) // concatenate elements
println("\"*\".join([\"1\", \"2\", \"3\"]) is \"\(joinResult)\"")

以下是对代码中各部分的详细解释:
1. String 变量和常量
- 第 10 - 12 行创建了 String 变量 s1 s3 ,以及 String 常量 s2 ,用于后续测试。
- 第 15 行显示这些字符串,以便确认程序的后续输出是否正确。
2. String 比较运算符
- String 类型符合 Equatable Comparable 协议,因此可以使用比较运算符 == != > < >= <= 来比较对象。
- 第 18 - 23 行展示了使用 String 重载的比较运算符比较 s2 s1 的结果。
- Swift 的 String 类型提供了完整的 Unicode 支持,字符串比较基于所谓的扩展字形集群进行,考虑了底层字符的语言意义和外观。即使两个字符串由不同的 Unicode 字符组成,但在屏幕上显示相同,它们也会被视为相等。相关详细信息可参考 此处
- 符合 Equatable 协议的类型的对象可以使用 == != 运算符进行比较,此类类型只需定义 == 运算符,Swift 标准库为 != 提供了一个通用的重载运算符函数,返回 == 的相反结果。
- Comparable 协议继承自 Equatable 协议,因此符合 Comparable 协议的对象也是 Equatable 对象。符合 Comparable 协议的类型的对象可以使用 < <= > >= == != 运算符进行比较,此类类型只需定义 < == 运算符,Swift 标准库为 <= > >= != 提供了通用的重载运算符函数,使用 < == 运算符返回适当的结果。
3. 自定义 String 一元前缀运算符 !
- String 类型的 isEmpty 属性在字符串不包含任何字符时返回 true 。为了演示,第 5 - 7 行定义了一个重载运算符函数,指定了 ! 运算符对 String 对象的操作方式。
- 重载运算符定义为全局函数,函数名是运算符符号,参数列表指定操作数。对于一元运算符,运算符函数的参数列表必须包含一个参数。定义重载一元运算符时,必须使用 prefix postfix 声明修饰符来指定运算符是前缀还是后缀一元运算符。
- 当编译器在第 28 行的条件中遇到表达式 !s3 时,会调用第 5 - 7 行的函数,将操作数 s3 作为函数的参数传递。由于 s3 为空,条件求值为 true ,因此第 29 行表明 s3 为空,第 30 行将 s1 赋值给 s3 ,第 31 行显示赋值后 s3 的内容。
4. 使用运算符 + += 进行字符串连接
- 第 36 - 38 行演示了 String 类型重载的 + += 运算符用于字符串连接。
- 第 36 行使用 + 运算符将 s2 的内容追加到 s1 后面,返回一个包含结果的新字符串,不修改操作数 s1 s2
- 第 37 行使用 String 重载的 += 运算符将 s2 的内容追加到 s1 后面,并将结果存储在左操作数中。与大多数基于 C 的编程语言不同,Swift 的赋值运算符不返回值,因此不能使用字符串插值将表达式 s1 += s2 的结果放入字符串中输出。第 38 行显示赋值后 s1 的新内容。
5. 用于创建子字符串的 String 下标 [] 运算符
- 可以使用下标 [] Int 值来访问数组的元素,以及使用字典键类型的下标来访问或存储字典中的相应值。第 41 - 46 行演示了如何使用下标 [] 从字符串中提取子字符串。
- 下标中放置的值是 String.Index 值的范围。字符串中的第一个字符的索引为 0。每个字符串包含两个该类型的属性: startIndex 是字符串第一个字符的 String.Index endIndex 是字符串最后一个字符之后的位置的 String.Index
- 可以使用全局函数 advance 创建 String.Index ,该函数接受一个 String.Index 作为第一个参数和一个 Int 作为第二个参数,将 String.Index 增加第二个参数的值,并返回表示该字符位置的 String.Index
- 第 43 行使用下标 s1.startIndex ..< index 返回一个包含从 s1 开头到 index 位置(不包括 index 位置的字符)的子字符串。
- 第 45 行使用下标 index ..< s1.endIndex 返回一个包含从 index 位置到字符串末尾的子字符串。如果尝试访问超出字符串边界的子字符串,会发生运行时错误。
6. 其他 String 方法
- 第 50 行使用 hasPrefix 方法确定 s1 是否以字符串参数的值 "hap" 开头,如果是则返回 true ,否则返回 false
- 第 52 行使用 hasSuffix 方法确定 s1 是否以字符串参数的值 "day" 结尾,如果是则返回 true ,否则返回 false
- 第 55 行使用 removeAtIndex 方法移除指定 String.Index 位置的字符,这里移除了 "happy birthday" 中的空格。
- 第 58 行使用 insert 方法在第二个参数指定的 String.Index 位置插入第一个参数的字符,这里重新插入了空格。
- 第 61 行使用 append 方法将其 Character 参数添加到 s1 的末尾。
- 第 64 行使用 removeAll 方法清空字符串 s1
- 第 67 行演示了 join 方法,该方法将其 [String] 参数的元素连接起来,在每对元素之间插入调用该方法的字符串 "*"

自定义复数类型的重载算术运算符

以下是一个自定义复数类型 Complex 并为其重载算术运算符的示例代码:

// Fig. 12.2: Complex.swift (ComplexNumbers.xcodeproj)
// Defining operators for a custom type Complex
import Foundation

public struct Complex: Printable {
    public var real: Double = 0.0
    public var imaginary: Double = 0.0

    public var description: String {
        return String(format: "(%.1f %@ %.1fi)", real,
                      imaginary < 0 ? "-" : "+", abs(imaginary))
    }
}

// overload the addition operator
public func +(x: Complex, y: Complex) -> Complex {
    return Complex(real: x.real + y.real,         
                   imaginary: x.imaginary + y.imaginary)     
}                                                 

// overload the subtraction operator
public func -(x: Complex, y: Complex) -> Complex {
    return Complex(real: x.real - y.real,         
                   imaginary: x.imaginary - y.imaginary)     
}                                                 

// overload the multiplication operator
public func *(x: Complex, y: Complex) -> Complex {                   
    return Complex(real: x.real * y.real - x.imaginary * y.imaginary,
                   imaginary: x.real * y.imaginary + y.real * x.imaginary)      
}                                                                    

// overload the addition assignment operator
public func +=(inout left: Complex, right: Complex) {
    left = left + right // uses overloaded +         
}                                                    

以下是对代码中各部分的详细解释:
1. Complex 结构体定义
- 第 5 - 13 行定义了一个 Complex 结构体,用于表示复数,即形式为 a + bi 的数,其中 a 是实部, b 是虚部。
- 结构体包含 Double 类型的属性 real imaginary 分别表示复数的实部和虚部,以及一个 description 属性,用于返回复数的字符串表示。
2. 重载加法运算符 +
- 第 16 - 19 行定义了一个重载的 + 运算符函数,用于将两个 Complex 数相加。
- 对于二元中缀运算符,运算符函数的参数列表必须包含两个参数,第一个参数是运算符的左操作数,第二个参数是运算符的右操作数。该重载 + 运算符接受两个 Complex 数并返回一个包含它们和的 Complex 数。
- 第 17 - 18 行将参数的值相加,并将结果作为一个新的 Complex 返回。注意,我们不会修改作为参数传递的原始操作数 x y 的内容,这符合我们对该运算符行为的直觉。
3. 重载减法运算符 -
- 第 22 - 25 行定义了一个重载的 - 运算符函数,用于将两个 Complex 数相减。
- 实现方式与加法运算符类似,返回一个包含两个 Complex 数差的 Complex 数。
4. 重载乘法运算符 *
- 第 28 - 31 行定义了一个重载的 * 运算符函数,用于将两个 Complex 数相乘。
- 根据复数乘法的规则计算结果,并返回一个新的 Complex 数。
5. 重载加法赋值运算符 +=
- 第 34 - 36 行定义了一个重载的 += 运算符函数,用于将一个 Complex 数加到另一个 Complex 数上,并将结果存储在左操作数中。
- 该函数使用了重载的 + 运算符。

通过以上的学习,我们了解了关联类型、泛型、运算符重载以及 String 类型和自定义复数类型的相关操作。运算符重载为我们提供了更自然和简洁的方式来操作对象,提高了代码的可读性和可维护性。在后续的学习中,我们还将继续探索更多关于运算符重载和下标的内容。

Swift 中的关联类型、运算符重载与下标

为 NSDecimalNumber 类重载算术运算符

在进行数学类型的算术运算时,使用方法调用的方式可能会比较繁琐。对于 NSDecimalNumber 类,我们可以通过运算符重载来简化计算。以下是为 NSDecimalNumber 类重载乘法运算符 * 、加法运算符 + 和乘法赋值运算符 *= 的示例代码:

// 重载乘法运算符 *
func *(lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> NSDecimalNumber {
    return lhs.decimalNumberByMultiplyingBy(rhs)
}

// 重载加法运算符 +
func +(lhs: NSDecimalNumber, rhs: NSDecimalNumber) -> NSDecimalNumber {
    return lhs.decimalNumberByAdding(rhs)
}

// 重载乘法赋值运算符 *=
func *=(inout left: NSDecimalNumber, right: NSDecimalNumber) {
    left = left.decimalNumberByMultiplyingBy(right)
}

// 使用重载运算符
let num1 = NSDecimalNumber(string: "2.5")
let num2 = NSDecimalNumber(string: "3.0")
let product = num1 * num2
let sum = num1 + num2
num1 *= num2

print("Product: \(product)")
print("Sum: \(sum)")
print("After *=: \(num1)")

以下是对代码的详细解释:
1. 重载乘法运算符 * :定义了一个全局函数,接受两个 NSDecimalNumber 类型的参数,使用 decimalNumberByMultiplyingBy 方法计算它们的乘积并返回结果。
2. 重载加法运算符 + :同样定义了一个全局函数,接受两个 NSDecimalNumber 类型的参数,使用 decimalNumberByAdding 方法计算它们的和并返回结果。
3. 重载乘法赋值运算符 *= :定义了一个接受 inout 参数的函数,将左操作数乘以右操作数,并将结果赋值给左操作数。
4. 使用重载运算符 :创建两个 NSDecimalNumber 对象,使用重载的运算符进行乘法、加法和乘法赋值操作,并打印结果。

重载一元运算符 ++ --

一元运算符 ++ -- 常用于递增和递减变量的值。在 Swift 中,我们可以为自定义类型重载这些运算符。

// 重载一元前缀递增运算符 ++
prefix func ++(inout num: Int) -> Int {
    num += 1
    return num
}

// 重载一元后缀递增运算符 ++
postfix func ++(inout num: Int) -> Int {
    let temp = num
    num += 1
    return temp
}

// 重载一元前缀递减运算符 --
prefix func --(inout num: Int) -> Int {
    num -= 1
    return num
}

// 重载一元后缀递减运算符 --
postfix func --(inout num: Int) -> Int {
    let temp = num
    num -= 1
    return temp
}

// 使用重载的一元运算符
var value = 5
let preIncrement = ++value
let postIncrement = value++
let preDecrement = --value
let postDecrement = value--

print("Pre-increment: \(preIncrement)")
print("Post-increment: \(postIncrement)")
print("Pre-decrement: \(preDecrement)")
print("Post-decrement: \(postDecrement)")

以下是对代码的详细解释:
1. 重载一元前缀递增运算符 ++ :使用 prefix 关键字定义,接受一个 inout 类型的 Int 参数,将其值加 1 并返回。
2. 重载一元后缀递增运算符 ++ :使用 postfix 关键字定义,接受一个 inout 类型的 Int 参数,先保存原始值,然后将其值加 1,最后返回原始值。
3. 重载一元前缀递减运算符 -- :与前缀递增运算符类似,将参数的值减 1 并返回。
4. 重载一元后缀递减运算符 -- :与后缀递增运算符类似,先保存原始值,然后将其值减 1,最后返回原始值。
5. 使用重载的一元运算符 :创建一个 Int 变量,使用重载的运算符进行递增和递减操作,并打印结果。

Swift 的 AnyObject 类型:Objective - C 和 Swift 之间的桥接

AnyObject 类型在 Swift 和 Objective - C 之间的交互中起着重要作用。在 Swift 中,任何类的实例都可以被视为 AnyObject 类型。这使得在 Swift 代码中可以方便地使用 Objective - C 类。例如,当你需要调用 Objective - C 框架中的类时,可以将其实例赋值给 AnyObject 类型的变量。

import Foundation

// 创建一个 Objective - C 类的实例
let date = NSDate()
// 将其赋值给 AnyObject 类型的变量
let anyObj: AnyObject = date
// 可以调用其方法
let timeInterval = anyObj.timeIntervalSince1970
print("Time interval since 1970: \(timeInterval)")

在这个示例中,我们创建了一个 NSDate 类的实例,将其赋值给 AnyObject 类型的变量 anyObj ,然后调用了 timeIntervalSince1970 方法。

重载下标

下标是一种方便的语法,用于访问集合、序列或容器中的元素。我们可以为自定义类型定义下标。以下是一个带有自定义下标的 Box 类型的示例:

class Box<T> {
    private var items: [T] = []

    // 自定义 Int 下标
    subscript(index: Int) -> T {
        get {
            precondition(index >= 0 && index < items.count, "Index out of range")
            return items[index]
        }
        set {
            precondition(index >= 0 && index < items.count, "Index out of range")
            items[index] = newValue
        }
    }

    // 自定义 String 下标
    subscript(key: String) -> T? {
        // 这里可以根据 key 实现自定义逻辑,这里简单返回 nil
        return nil
    }

    func addItem(item: T) {
        items.append(item)
    }
}

// 使用 Box 类型的下标
let box = Box<Int>()
box.addItem(10)
box.addItem(20)
let firstItem = box[0]
let nonExistentKey = box["key"]

print("First item: \(firstItem)")
print("Value for key: \(nonExistentKey)")

以下是对代码的详细解释:
1. Box 类定义 :这是一个泛型类,包含一个私有数组 items 用于存储元素。
2. 自定义 Int 下标
- get 方法:使用 precondition 函数确保索引在有效范围内,然后返回对应索引的元素。
- set 方法:同样使用 precondition 函数确保索引在有效范围内,然后将新值赋给对应索引的元素。
3. 自定义 String 下标 :这里只是简单返回 nil ,你可以根据实际需求实现根据字符串键查找元素的逻辑。
4. 使用 Box 类型的下标 :创建一个 Box 实例,添加元素,使用 Int 下标访问元素,使用 String 下标尝试获取元素,并打印结果。

自定义运算符

除了重载现有的运算符,我们还可以创建全新的运算符。在创建自定义运算符时,需要考虑运算符的优先级和结合性。

// 定义一个自定义运算符
infix operator ** { associativity left precedence 160 }

// 为 Int 类型定义自定义指数运算符
func **(base: Int, exponent: Int) -> Int {
    return Int(pow(Double(base), Double(exponent)))
}

// 使用自定义运算符
let result = 2 ** 3
print("2 to the power of 3: \(result)")

以下是对代码的详细解释:
1. 定义自定义运算符 :使用 infix 关键字定义一个中缀运算符 ** ,指定其结合性为左结合,优先级为 160。
2. Int 类型定义自定义指数运算符 :定义一个函数,接受两个 Int 类型的参数,使用 pow 函数计算指数结果并转换为 Int 类型返回。
3. 使用自定义运算符 :使用自定义的指数运算符计算 2 3 次方,并打印结果。

自定义泛型运算符

自定义泛型运算符可以用于多种类型。以下是一个自定义泛型运算符的示例:

// 定义一个自定义泛型运算符
infix operator <+> { associativity left precedence 140 }

// 为支持加法的类型定义自定义泛型运算符
func <+><T: protocol<Equatable, Addable>>(lhs: T, rhs: T) -> T {
    return lhs + rhs
}

// 定义一个支持加法的协议
protocol Addable {
    static func +(lhs: Self, rhs: Self) -> Self
}

// 让 Int 类型遵循 Addable 协议
extension Int: Addable {}

// 使用自定义泛型运算符
let intResult = 5 <+> 3
print("Int result: \(intResult)")

以下是对代码的详细解释:
1. 定义自定义泛型运算符 :使用 infix 关键字定义一个中缀运算符 <+> ,指定其结合性为左结合,优先级为 140。
2. 为支持加法的类型定义自定义泛型运算符 :定义一个泛型函数,要求类型 T 遵循 Equatable Addable 协议,使用 + 运算符计算结果并返回。
3. 定义 Addable 协议 :定义一个协议,要求类型实现 + 运算符。
4. Int 类型遵循 Addable 协议 :通过扩展让 Int 类型遵循 Addable 协议。
5. 使用自定义泛型运算符 :使用自定义泛型运算符对两个 Int 类型的值进行加法运算并打印结果。

总结

通过以上内容,我们深入学习了 Swift 中的关联类型、运算符重载和下标。关联类型为协议提供了泛型支持;运算符重载使我们能够为自定义类型使用现有的运算符,提高了代码的可读性和简洁性;下标为自定义类型提供了方便的元素访问方式;自定义运算符和泛型运算符则进一步扩展了 Swift 的表达能力。在使用运算符重载和自定义运算符时,需要遵循一些良好的编程实践,如避免过度或不一致的使用,确保运算符的行为符合直觉,以提高代码的可维护性。

以下是一个总结表格:
| 主题 | 说明 |
| ---- | ---- |
| 关联类型 | 在协议中使用的泛型类型,由符合协议的类型确定实际类型 |
| 运算符重载 | 除赋值运算符 = 和三元条件运算符 ?: 外,可重载其他内置运算符,使运算符适用于自定义类型 |
| 重载 String 运算符 | 包括比较运算符、一元前缀运算符 ! 、连接运算符 + += 、下标运算符 [] 等 |
| 自定义复数类型运算符 | 为 Complex 类型重载加法、减法、乘法和加法赋值运算符 |
| 重载 NSDecimalNumber 运算符 | 重载乘法、加法和乘法赋值运算符 |
| 重载一元运算符 ++ -- | 包括前缀和后缀形式 |
| AnyObject 类型 | 用于 Swift 和 Objective - C 之间的桥接 |
| 重载下标 | 为自定义类型定义下标,支持不同类型的索引 |
| 自定义运算符 | 创建全新的运算符,需指定优先级和结合性 |
| 自定义泛型运算符 | 可用于多种类型的自定义运算符 |

以下是一个简单的 mermaid 流程图,展示了运算符重载的基本流程:

graph TD;
    A[定义自定义类型] --> B[确定要重载的运算符];
    B --> C[定义重载运算符的函数];
    C --> D[在函数中实现运算符逻辑];
    D --> E[使用重载运算符进行操作];

通过掌握这些知识,你可以更加灵活地使用 Swift 进行编程,编写出更具表现力和可读性的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值