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 进行编程,编写出更具表现力和可读性的代码。
超级会员免费看
293

被折叠的 条评论
为什么被折叠?



