Swift 中的运算符重载与下标重载
1. 复数运算的运算符重载
在 Swift 中,对复数进行加法操作不会修改原始的复数对象,因为 Complex 是值类型,操作的是参数的副本。同时,也为复数的减法和乘法提供了类似的运算符函数。
1.1 软件工程观察
在进行运算符重载时,有以下几个重要的软件工程观察点:
- 避免非直观的运算符使用 :程序员期望二元加法运算符 + 执行加法(如 Swift 的数值类型)或拼接(如字符串)操作。当重载常用运算符时,应使其对自定义类型的对象执行与其他类型对象相同或相似的任务。
- 算术运算符重载不自动重载赋值运算符 :重载算术运算符(如 * )不会自动重载相应的赋值运算符(如 *= )。
1.2 重载加法赋值运算符 +=
Fig. 12.2 中重载的加法 + 、减法 - 和乘法 * 运算符不会修改其复数操作数。可以通过重载 += 加法赋值运算符来简化代码。示例代码如下:
// 重载 += 运算符
func +=(inout left: Complex, right: Complex) {
left = left + right
}
赋值运算符的左操作数必须声明为 inout 参数,以便函数可以用计算结果替换左操作数的值。
1.3 软件工程观察与错误预防
- 赋值运算符不返回值 :与许多基于 C 的编程语言不同,Swift 的赋值运算符不返回值,因此重载的赋值运算符函数不需要指定返回类型。
- 避免意外使用
=代替==:由于赋值运算符不返回值,因此在条件语句中不会意外使用=代替==。 - 重载赋值运算符时左操作数的处理 :重载赋值运算符时,重载运算符函数的第一个参数表示赋值运算符的左操作数,该参数必须声明为
inout,以便重载运算符函数可以修改左操作数。
1.4 复数运算示例
以下是一个演示复数运算符 + 、 - 和 * 重载的程序:
// Fig. 12.3: main.swift (ComplexNumbers.xcodeproj)
// Overloading operators for complex numbers.
// create two Complex numbers
var x = Complex(real: 2.1, imaginary: 4.2)
var y = Complex(real: 5.2, imaginary: -1.1)
println("Complex x: \(x)")
println("Complex y: \(y)\n")
println("CALCULATION RESULTS")
println("\(x) + \(y) = \(x + y)")
println("\(x) - \(y) = \(x - y)")
println("\(x) * \(y) = \(x * y)")
println("ADDITION ASSIGNMENT OPERATOR")
x += y
println("After x += y, x = \(x)")
运行结果如下:
Complex x: (2.1 + 4.2i)
Complex y: (5.2 - 1.1i)
CALCULATION RESULTS
(2.1 + 4.2i) + (5.2 - 1.1i) = (7.3 + 3.1i)
(2.1 + 4.2i) - (5.2 - 1.1i) = (-3.1 + 5.3i)
(2.1 + 4.2i) * (5.2 - 1.1i) = (15.5 + 19.5i)
ADDITION ASSIGNMENT OPERATOR
After x += y, x = (7.3 + 3.1i)
在这个示例中,使用重载的运算符进行复数的加、减和乘运算,并输出结果。
2. 为 NSDecimalNumber 类重载算术运算符
不仅可以为 Swift 类型重载运算符,还可以为现有的 Cocoa 和 Cocoa Touch 类(或第三方库中的类型)重载运算符。
2.1 复利计算示例
Fig. 12.4 展示了一个更新后的复利计算示例,使用重载的乘法 * 和加法 + 运算符来进行复利计算,使代码更加自然和简洁。示例代码如下:
// Fig. 12.4: CompoundInterest.playground
// Overloading operators for NSDecimalNumber
import Foundation
// format a String right aligned in a field
func rightAlignedString(string: String, fieldWidth: Int) -> String {
let spaces: Int = fieldWidth - countElements(string)
let padding = String(count: spaces, repeatedValue: Character(" "))
return padding + string
}
// overloaded * operator to multiply NSDecimalNumbers
func *(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber {
return left.decimalNumberByMultiplyingBy(right)
}
// overloaded + operator to add NSDecimalNumbers
func +(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber {
return left.decimalNumberByAdding(right)
}
var amount = NSDecimalNumber(string: "1000.00") // amount before interest
let rate = NSDecimalNumber(string: "0.05") // interest rate
// display headers
println(String(format: "%@%@", "Year", rightAlignedString("Amount on deposit", 20)))
// calculate amount on deposit for each of ten years
for year in 1...5 {
// calculate new amount for specified year using overloaded operators
amount = amount * (rate + NSDecimalNumber.one())
let formattedAmount = NSNumberFormatter.localizedStringFromNumber(
amount, numberStyle: .CurrencyStyle)
// display the year and the amount
println(String(format: "%4d%@", year, rightAlignedString(formattedAmount, 20)))
}
运行结果如下:
Year Amount on deposit
1 $1,050.00
2 $1,102.50
3 $1,157.62
4 $1,215.51
5 $1,276.28
2.2 重载乘法运算符 *
func *(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber {
return left.decimalNumberByMultiplyingBy(right)
}
该函数接收两个 NSDecimalNumber 类型的操作数,并返回它们相乘的结果。
2.3 重载加法运算符 +
func +(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber {
return left.decimalNumberByAdding(right)
}
该函数接收两个 NSDecimalNumber 类型的操作数,并返回它们相加的结果。
2.4 使用重载运算符
使用重载的 * 和 + 运算符可以简化复利计算的代码:
amount = amount * (rate + NSDecimalNumber.one())
相比于未使用重载运算符的代码,更加简洁易读。
2.5 重载乘法赋值运算符 *=
可以进一步重载 *= 乘法赋值运算符来简化代码:
func *=(inout left: NSDecimalNumber, right: NSDecimalNumber) {
left = left * right
}
使用 *= 运算符后,代码可以简化为:
amount *= rate + NSDecimalNumber.one()
3. 重载一元运算符 ++ 和 --
在本节中,将为 NSDecimalNumber 重载一元运算符 ++ 和 -- 。
3.1 示例代码
// Fig. 12.5: PrefixAndPostfix.playground
// Overloading ++ and -- operators for NSDecimalNumber
import Foundation
// make NSDecimalNumbers Printable for use in string interpolation
extension NSDecimalNumber : Printable {}
// add 1 to an NSDecimalNumber and return the new value
prefix func ++(inout number: NSDecimalNumber) -> NSDecimalNumber {
number = number.decimalNumberByAdding(NSDecimalNumber.one())
return number
}
// add 1 to an NSDecimalNumber and return the old value
postfix func ++(inout number: NSDecimalNumber) -> NSDecimalNumber {
let temp = number.copy() as NSDecimalNumber
number = number.decimalNumberByAdding(NSDecimalNumber.one())
return temp
}
// subtract 1 from an NSDecimalNumber and return the new value
prefix func --(inout number: NSDecimalNumber) -> NSDecimalNumber {
number = number.decimalNumberBySubtracting(NSDecimalNumber.one())
return number
}
// subtract 1 from an NSDecimalNumber and return the old value
postfix func --(inout number: NSDecimalNumber) -> NSDecimalNumber {
let temp = number.copy() as NSDecimalNumber
number = number.decimalNumberBySubtracting(NSDecimalNumber.one())
return temp
}
// demonstrate postfix increment operator
var decimalValue = NSDecimalNumber(string: "5.5")
println("decimalValue before postincrement: \(decimalValue)") // 5.5
println("postincrementing decimalValue: \(decimalValue++)") // 5.5
println("decimalValue after postincrement: \(decimalValue)\n") // 6.5
// demonstrate prefix increment operator
println("decimalValue before preincrement: \(decimalValue)") // 6.5
println("preincrementing decimalValue: \(++decimalValue)") // 7.5
println("decimalValue after preincrement: \(decimalValue)\n") // 7.5
// demonstrate postfix decrement operator
println("decimalValue before postdecrement: \(decimalValue)") // 7.5
println("postdecrementing decimalValue: \(decimalValue--)") // 7.5
println("decimalValue after postdecrement: \(decimalValue)\n") // 6.5
// demonstrate prefix decrement operator
println("decimalValue before predecrement: \(decimalValue)") // 6.5
println("predecrementing decimalValue: \(--decimalValue)") // 5.5
println("decimalValue after predecrement: \(decimalValue)") // 5.5
3.2 重载前缀一元运算符
- 重载
++前缀运算符 :
prefix func ++(inout number: NSDecimalNumber) -> NSDecimalNumber {
number = number.decimalNumberByAdding(NSDecimalNumber.one())
return number
}
该函数将操作数加 1 并返回新值。
- 重载 -- 前缀运算符 :
prefix func --(inout number: NSDecimalNumber) -> NSDecimalNumber {
number = number.decimalNumberBySubtracting(NSDecimalNumber.one())
return number
}
该函数将操作数减 1 并返回新值。
3.3 重载后缀一元运算符
- 重载
++后缀运算符 :
postfix func ++(inout number: NSDecimalNumber) -> NSDecimalNumber {
let temp = number.copy() as NSDecimalNumber
number = number.decimalNumberByAdding(NSDecimalNumber.one())
return temp
}
该函数将操作数加 1 并返回旧值。
- 重载 -- 后缀运算符 :
postfix func --(inout number: NSDecimalNumber) -> NSDecimalNumber {
let temp = number.copy() as NSDecimalNumber
number = number.decimalNumberBySubtracting(NSDecimalNumber.one())
return temp
}
该函数将操作数减 1 并返回旧值。
3.4 Swift 的 AnyObject 类型
在 Swift 中, NSObject 的 copy 方法返回一个 NSObject ,当从 Foundation 类方法返回 NSObject 到 Swift 程序时,Swift 运行时将其视为 AnyObject 。为了在 Swift 代码中使用 AnyObject ,需要将其转换为适当的类型。例如,在重载 ++ 和 -- 运算符时,使用 as 强制类型转换将 copy 方法的返回值转换为 NSDecimalNumber 。
3.5 桥接 Swift 和 Objective-C 类型
Swift 和 Objective-C 类型之间存在桥接机制,使得它们可以相互转换。例如, NSString 可以桥接到 Swift 的 String 类型, NSArray 可以桥接到 Swift 的 [AnyObject] 类型。更多关于 Swift 和 Objective-C 类型桥接的详细信息,可以参考 Apple 的文档 。
4. 重载下标
在 Swift 中,可以为任何类、结构体或枚举类型定义自定义下标。
4.1 带有自定义下标的 Box 类型
Fig. 12.6 定义了一个名为 Box 的结构体,它存储盒子的长度、宽度和高度。通过定义两个重载的下标,可以通过整数索引或字符串名称访问这些维度。示例代码如下:
// Fig. 12.6: Box.swift (Subscripts.xcodeproj)
// Box type with dimensions accessed via subscripts
import Foundation
public struct Box {
private static let names = ["length", "width", "height"]
private var dimensions = [0.0, 0.0, 0.0]
// initializer
public init(length: Double, width: Double, height: Double) {
dimensions[0] = length
dimensions[1] = width
dimensions[2] = height
}
// subscript to access dimensions by their Int indices
public subscript(index: Int) -> Double {
get {
precondition(index >= 0 && index < dimensions.count)
return dimensions[index]
}
set {
precondition(index >= 0 && index < dimensions.count)
dimensions[index] = newValue
}
}
// subscript to access dimensions by their String names
public subscript(name: String) -> Double {
get {
precondition(
Box.names.filter({$0 == name.lowercaseString}).count == 1)
return dimensions[nameToIndex(name)]
}
set {
precondition(
Box.names.filter({$0 == name.lowercaseString}).count == 1)
dimensions[nameToIndex(name)] = newValue
}
}
// utility function to convert a name into a dimensions Array index
private func nameToIndex(name: String) -> Int {
var i = 0
while i < Box.names.count {
if name.lowercaseString == Box.names[i] {
return i // name is in names at position i
}
++i
}
return -1 // preconditions in subscript(name: String) prevent this
}
}
4.2 下标语法
下标可以在类型或类型的扩展中定义,其语法如下:
subscript(parameterList) -> Type {
get {
// code to return a value
}
set {
// code to set a value
}
}
参数列表是一个用逗号分隔的列表,包含一个或多个参数。 Type 是必需的 get 访问器返回的值的类型或可选的 set 访问器传递的值的类型。
4.3 Box 类型的整数下标
对于带有整数参数 index 的下标:
- get 访问器 :返回 Box 的 dimensions 数组中该索引处的元素。
- set 访问器 :将新值赋给 dimensions 数组中该索引处的元素。
在访问和设置元素之前,使用 precondition 函数验证索引是否在数组的有效范围内。
4.4 Box 类型的字符串下标
对于带有字符串参数 name 的下标:
- get 访问器 :使用 nameToIndex 方法确定 name 在 Box 的 names 数组中的索引,然后返回 dimensions 数组中该索引处的元素。
- set 访问器 :使用 nameToIndex 方法确定 name 在 Box 的 names 数组中的索引,然后将新值赋给 dimensions 数组中该索引处的元素。
同样,在访问和设置元素之前,使用 precondition 函数验证名称是否有效。
4.5 使用 Box 类型的下标
以下是一个演示 Box 类型下标使用的示例:
// 假设 Box 结构体已定义
var box = Box(length: 10.0, width: 20.0, height: 30.0)
// 通过整数下标获取长度、宽度和高度
let length = box[0]
let width = box[1]
let height = box[2]
// 通过整数下标设置长度
box[0] = 15.0
// 通过字符串下标设置宽度
box["width"] = 25.0
// 通过字符串下标获取长度、宽度和高度
let newLength = box["length"]
let newWidth = box["width"]
let newHeight = box["height"]
通过这些下标,可以方便地访问和修改 Box 结构体的维度。
4.6 precondition 和 assert 函数
-
precondition函数 :用于验证前置条件,如果条件为false,则终止程序执行。有单参数和双参数两种版本,双参数版本可以指定错误消息。 -
assert函数 :与precondition函数类似,但仅在调试模式下执行。
4.7 总结
通过重载运算符和下标,可以使代码更加简洁、自然和易读。在实际开发中,可以根据需要为自定义类型重载合适的运算符和下标,提高代码的可维护性和可读性。
4.8 表格:运算符重载总结
| 运算符 | 重载说明 | 示例代码 |
|---|---|---|
+ | 用于复数加法和 NSDecimalNumber 加法 | func +(left: Complex, right: Complex) -> Complex func +(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber |
- | 用于复数减法 | func -(left: Complex, right: Complex) -> Complex |
* | 用于复数乘法和 NSDecimalNumber 乘法 | func *(left: Complex, right: Complex) -> Complex func *(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber |
+= | 用于复数加法赋值 | func +=(inout left: Complex, right: Complex) |
*= | 用于 NSDecimalNumber 乘法赋值 | func *=(inout left: NSDecimalNumber, right: NSDecimalNumber) |
++ | 用于 NSDecimalNumber 前缀和后缀自增 | prefix func ++(inout number: NSDecimalNumber) -> NSDecimalNumber postfix func ++(inout number: NSDecimalNumber) -> NSDecimalNumber |
-- | 用于 NSDecimalNumber 前缀和后缀自减 | prefix func --(inout number: NSDecimalNumber) -> NSDecimalNumber postfix func --(inout number: NSDecimalNumber) -> NSDecimalNumber |
4.9 流程图: Box 类型下标访问流程
graph TD;
A[开始] --> B{选择下标类型};
B -->|整数下标| C{验证索引范围};
C -->|有效| D[访问或设置元素];
C -->|无效| E[终止程序];
B -->|字符串下标| F{验证名称有效性};
F -->|有效| G[获取索引];
G --> H[访问或设置元素];
F -->|无效| E;
D --> I[结束];
H --> I;
E --> I;
这个流程图展示了 Box 类型下标访问的流程,包括整数下标和字符串下标的验证和访问过程。
通过以上内容,我们详细介绍了 Swift 中运算符重载和下标重载的相关知识,包括复数运算、 NSDecimalNumber 运算、一元运算符重载以及自定义下标的使用。这些技术可以使代码更加灵活和易读,提高开发效率。
5. 运算符重载和下标重载的优势与应用场景
5.1 优势
- 代码简洁性 :通过重载运算符和下标,可以使代码更加简洁。例如,在复利计算中,使用重载的
*和+运算符,让计算语句amount = amount * (rate + NSDecimalNumber.one())比直接调用方法更加简洁明了。 - 可读性增强 :重载运算符和下标可以让代码更符合自然语言的表达习惯。比如在复数运算中,使用
+、-和*运算符进行复数的加、减、乘运算,让代码更易于理解。 - 可维护性提高 :当代码中使用了重载的运算符和下标后,后续的维护人员可以更容易理解代码的意图,减少出错的可能性。
5.2 应用场景
- 数学计算 :在处理复数、矩阵等数学对象时,运算符重载可以方便地实现各种数学运算。例如,复数的加、减、乘运算,矩阵的加法、乘法等。
- 数据结构操作 :对于自定义的数据结构,下标重载可以提供方便的元素访问方式。比如
Box结构体,通过下标可以方便地访问和修改盒子的长度、宽度和高度。 - 框架集成 :在集成第三方框架或使用 Cocoa 和 Cocoa Touch 类时,运算符重载可以让代码与框架的交互更加自然。例如,为
NSDecimalNumber重载运算符,使复利计算的代码更加简洁。
5.3 表格:优势与应用场景总结
| 优势 | 应用场景 | 示例 |
|---|---|---|
| 代码简洁性 | 数学计算 | 复数运算、矩阵运算 |
| 可读性增强 | 数据结构操作 | Box 结构体的下标访问 |
| 可维护性提高 | 框架集成 | 为 NSDecimalNumber 重载运算符 |
6. 注意事项与最佳实践
6.1 注意事项
- 避免过度重载 :虽然运算符重载和下标重载可以带来很多好处,但过度使用可能会导致代码的可读性下降。例如,重载一个不常用的运算符来执行一个不相关的操作,会让其他开发人员难以理解代码的意图。
- 遵循约定 :在重载运算符时,要遵循程序员的普遍期望。例如,加法运算符
+通常用于执行加法或拼接操作,不要用它来执行其他不相关的操作。 - 处理异常情况 :在重载运算符和下标时,要考虑各种异常情况。例如,在
Box结构体的下标实现中,使用precondition函数来验证索引和名称的有效性,避免越界访问。
6.2 最佳实践
- 明确目的 :在进行运算符重载和下标重载之前,要明确重载的目的,确保重载后的代码更加简洁、易读和可维护。
- 文档注释 :为重载的运算符和下标添加详细的文档注释,说明其功能和使用方法,方便其他开发人员理解和使用。
- 测试充分 :对重载的运算符和下标进行充分的测试,确保其在各种情况下都能正常工作。
6.3 流程图:运算符重载和下标重载的开发流程
graph TD;
A[明确需求] --> B{是否需要重载};
B -->|是| C[选择合适的运算符或下标];
C --> D[实现重载代码];
D --> E[添加文档注释];
E --> F[进行充分测试];
F --> G{测试是否通过};
G -->|是| H[集成到项目中];
G -->|否| D;
B -->|否| I[使用现有方式实现];
这个流程图展示了运算符重载和下标重载的开发流程,包括需求分析、选择、实现、注释、测试和集成等步骤。
7. 总结与展望
7.1 总结
本文详细介绍了 Swift 中的运算符重载和下标重载技术。通过复数运算、 NSDecimalNumber 运算、一元运算符重载以及自定义下标的示例,展示了这些技术的具体应用。运算符重载和下标重载可以使代码更加简洁、易读和可维护,提高开发效率。
7.2 展望
随着 Swift 语言的不断发展,运算符重载和下标重载可能会有更多的应用场景和优化。例如,未来可能会支持更多类型的运算符重载,或者提供更方便的下标定义方式。同时,在跨平台开发中,这些技术也可以更好地与其他平台的代码进行交互。
总之,掌握 Swift 中的运算符重载和下标重载技术,可以让我们在开发中更加灵活地处理各种数据和操作,写出高质量的代码。
超级会员免费看

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



