Swift

基础运算符

理解 Swift 中的基础运算符,包括赋值、算术、比较、逻辑、区间运算符以及运算符优先级和结合性。

运算符是用于检查、改变或组合值的特殊符号或短语。Swift 支持大多数标准 C 运算符,并改进了一些功能以消除常见的编程错误。

术语

运算符分为一元、二元和三元:

  • 一元运算符作用于单个目标(如 -a),可以是前缀运算符(出现在目标之前,如 !b)或后缀运算符(出现在目标之后,如 c!
  • 二元运算符作用于两个目标(如 2 + 3),是中缀运算符,出现在两个目标之间
  • 三元运算符作用于三个目标,Swift 只有一个三元运算符:三元条件运算符(a ? b : c

赋值运算符

赋值运算符 = 用于初始化或更新变量的值。

let b = 10
var a = 5
a = b
// a 现在等于 10

如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量。

let (x, y) = (1, 2)
// x 等于 1,y 等于 2

与 C 和 Objective-C 不同,Swift 的赋值运算符本身不返回值。以下语句是无效的:

if x = y {
    // 错误:x = y 不返回值
}

这个特性使你无法把 == 错写成 =,避免了这类错误的发生。

算术运算符

Swift 支持所有数值类型的四个标准算术运算符:

  • 加法 +
  • 减法 -
  • 乘法 *
  • 除法 /
1 + 2       // 等于 3
5 - 3       // 等于 2
2 * 3       // 等于 6
10.0 / 2.5  // 等于 4.0

与 C 和 Objective-C 不同,Swift 的算术运算符默认不允许值溢出。你可以使用 Swift 的溢出运算符来实现溢出运算(如 a &+ b)。

加法运算符也可用于字符串拼接:

"hello, " + "world"  // 等于 "hello, world"

一元负号运算符

数值的正负号可以使用前缀 - 切换,称为一元负号运算符。

let three = 3
let minusThree = -three       // minusThree 等于 -3
let plusThree = -minusThree   // plusThree 等于 3

一元负号运算符直接写在操作数之前,不加空格。

一元正号运算符

一元正号运算符 + 不做任何改变地返回操作数的值。

let minusSix = -6
let alsoMinusSix = +minusSix  // alsoMinusSix 等于 -6

虽然一元正号运算符什么都不做,但当你在使用一元负号来表达负数时,可以使用一元正号来表达正数,使代码更加对称。

求余运算符

求余运算符 % 计算 a 除以 b 的余数。

9 % 4    // 等于 1

计算过程:9 = (4 × 2) + 1,余数是 1

对于负数,求余运算符的行为如下:

-9 % 4   // 等于 -1

计算过程:-9 = (4 × -2) + (-1),余数是 -1

b 的正负号会被忽略,这意味着 a % ba % -b 的结果是相同的。

9 % -4   // 等于 1
-9 % -4  // 等于 -1

组合赋值运算符

Swift 提供了组合赋值运算符,将赋值运算符 = 与其他运算符组合。

var a = 1
a += 2
// a 现在等于 3

表达式 a += 2a = a + 2 的简写。组合赋值运算符将加法和赋值合并为一个操作。

组合赋值运算符不返回值。例如,你不能写 let b = a += 2

完整的组合赋值运算符列表可以在 Swift 标准库运算符声明中找到。

比较运算符

Swift 支持所有标准 C 比较运算符:

  • 等于 ==
  • 不等于 !=
  • 大于 >
  • 小于 <
  • 大于等于 >=
  • 小于等于 <=
1 == 1   // true
2 != 1   // true
2 > 1    // true
1 < 2    // true
1 >= 1   // true
2 <= 1   // false

比较运算符常用于条件语句,如 if 语句:

let name = "world"
if name == "world" {
    print("hello, world")
} else {
    print("对不起,\(name),我不认识你")
}
// 输出 "hello, world",因为 name 确实等于 "world"

元组比较

如果两个元组的元素数量相同且对应元素的类型相同,则可以比较它们。元组从左到右逐个比较,一次比较一个值,直到发现两个值不相等为止。

(1, "zebra") < (2, "apple")   // true,因为 1 < 2
(3, "apple") < (3, "bird")    // true,因为 3 == 3,且 "apple" < "bird"
(4, "dog") == (4, "dog")      // true,因为 4 == 4,且 "dog" == "dog"

只有当元组中的每个值都可以比较时,元组才能比较。例如,如下代码所示,你可以比较两个类型为 (String, Int) 的元组,因为 StringInt 都可以比较。相反,两个类型为 (String, Bool) 的元组不能比较,因为 Bool 类型不能比较。

("blue", -1) < ("purple", 1)        // 正确,结果为 true
("blue", false) < ("purple", true)  // 错误,因为 Bool 不能比较

Swift 标准库只能比较元素数量少于七个的元组。要比较七个或更多元素的元组,你必须自己实现比较运算符。

三元条件运算符

三元条件运算符是一个特殊的运算符,有三个部分,形式为 问题 ? 答案1 : 答案2。它是基于 问题 是真或假来决定执行两个表达式中的哪一个。

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight 等于 90

上面的例子等价于:

let contentHeight = 40
let hasHeader = true
var rowHeight = contentHeight
if hasHeader {
    rowHeight = rowHeight + 50
} else {
    rowHeight = rowHeight + 20
}
// rowHeight 等于 90

三元条件运算符提供了一个简洁的方式来决定考虑哪个值。使用三元条件运算符时要小心,它的简洁性如果使用不当会导致代码难以理解。避免在一个组合语句中使用多个三元条件运算符。

空合运算符

空合运算符 a ?? b 对可选类型 a 进行空判断,如果 a 包含一个值就进行解包,否则就返回一个默认值 b

表达式 a 必须是可选类型。默认值 b 的类型必须与 a 存储值的类型保持一致。

空合运算符是以下代码的简写形式:

a != nil ? a! : b

使用空合运算符的示例:

let defaultColorName = "red"
var userDefinedColorName: String?   // 默认为 nil

var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 为 nil,colorNameToUse 被设置为默认值 "red"

如果你给 userDefinedColorName 赋一个非空值,再次执行空合运算,运算结果为 userDefinedColorName 的值,而不是默认值:

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 非空,colorNameToUse 被设置为 "green"

区间运算符

Swift 提供了几种区间运算符,它们是表达一个范围的值的快捷方式。

闭区间运算符

闭区间运算符 a...b 定义一个包含从 ab(包括 ab)的所有值的区间。a 的值不能大于 b

闭区间运算符在迭代一个区间的所有值时很有用,如在 for-in 循环中:

for index in 1...5 {
    print("\(index) * 5 = \(index * 5)")
}
// 1 * 5 = 5
// 2 * 5 = 10
// 3 * 5 = 15
// 4 * 5 = 20
// 5 * 5 = 25

半开区间运算符

半开区间运算符 a..<b 定义一个从 ab 但不包括 b 的区间。之所以称为半开区间,是因为该区间包含第一个值而不包含最后的值。

半开区间在处理从零开始的列表(如数组)时特别有用,可以数到列表的长度但不包括长度本身:

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("第 \(i + 1) 个人是 \(names[i])")
}
// 第 1 个人是 Anna
// 第 2 个人是 Alex
// 第 3 个人是 Brian
// 第 4 个人是 Jack

数组有 4 个元素,但 0..<count 只数到 3(数组中最后一个元素的索引),因为它是半开区间。

单侧区间

闭区间运算符有另一种形式,可以表达向一个方向尽可能远的区间。例如,一个包含数组从索引 2 到结尾的所有元素的区间。在这种情况下,你可以省略区间运算符一侧的值。

let names = ["Anna", "Alex", "Brian", "Jack"]

for name in names[2...] {
    print(name)
}
// Brian
// Jack

for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian

半开区间运算符也有单侧形式,只写最终值:

for name in names[..<2] {
    print(name)
}
// Anna
// Alex

单侧区间不只是在下标中使用,也可以在其他情境中使用。你不能遍历省略了初始值的单侧区间,因为不知道遍历从哪里开始。但你可以遍历省略最终值的单侧区间,但因为这种区间无限延续,请确保在循环中添加一个明确的结束条件。

你也可以检查单侧区间是否包含某个特定的值:

let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

逻辑运算符

逻辑运算符修改或组合布尔逻辑值 truefalse。Swift 支持基于 C 语言的三个标准逻辑运算符:

  • 逻辑非 !a
  • 逻辑与 a && b
  • 逻辑或 a || b

逻辑非运算符

逻辑非运算符 !a 对一个布尔值取反,使得 truefalsefalsetrue

逻辑非运算符是一个前缀运算符,直接出现在操作值之前,不加空格。

let allowedEntry = false
if !allowedEntry {
    print("ACCESS DENIED")
}
// 输出 "ACCESS DENIED"

if !allowedEntry 可以读作"如果不允许进入"。只有在"不允许进入"为 true 时,即 allowedEntryfalse 时,该行才会被执行。

逻辑与运算符

逻辑与运算符 a && b 表达了只有 ab 的值都为 true 时,整个表达式的值才会是 true

只要任意一个值为 false,整个表达式的值就为 false。事实上,如果第一个值为 false,那么第二个值不会被计算,因为它已经不可能影响整个表达式的结果了。这被称为短路计算。

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// 输出 "ACCESS DENIED"

逻辑或运算符

逻辑或运算符 a || b 是一个由两个连续的 | 组成的中缀运算符。它表示了只要 ab 其中一个为 true,整个表达式就为 true

与逻辑与运算符类似,逻辑或运算符也使用短路计算。如果左侧的表达式为 true,右侧的表达式就不会被计算,因为它不会改变整个表达式的值。

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// 输出 "Welcome!"

组合逻辑运算符

你可以组合多个逻辑运算符来创建更长的复合表达式:

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// 输出 "Welcome!"

这个例子使用了多个 &&|| 运算符来创建一个更长的复合表达式。然而,&&|| 运算符仍然只操作两个值,所以这实际上是三个较小的表达式连在一起。

使用括号来明确意图

当复杂表达式的意图不明确时,使用括号来明确优先级是很有用的,即使括号在技术上不是必需的。

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// 输出 "Welcome!"

括号清楚地表明前两个值被视为整体逻辑中独立的一部分。虽然复合表达式的输出没有改变,但对于读者来说整体意图更加清晰。可读性总是优于简洁性,使用括号来让你的意图更明确。

运算符优先级和结合性

运算符优先级决定了在包含多个运算符的表达式中,哪些运算符先被执行。

let result = 2 + 3 * 4
// result 等于 14,而不是 20

乘法运算符 * 的优先级高于加法运算符 +,所以先计算 3 * 4 = 12,然后再计算 2 + 12 = 14

运算符结合性定义了相同优先级的运算符如何组合在一起,是从左到右还是从右到左。

let result = 10 - 5 - 2
// result 等于 3

减法运算符是左结合的,所以表达式从左到右计算:(10 - 5) - 2 = 3

Swift 中的运算符优先级和结合性规则比 C 和 Objective-C 更简单和可预测。完整的 Swift 运算符优先级和结合性规则可以在 Swift 标准库运算符声明中找到。

最佳实践

1. 使用空合运算符简化可选值处理

// 好的做法 - 使用空合运算符
let displayName = userName ?? "Guest"

// 避免 - 冗长的条件判断
let displayName: String
if let name = userName {
    displayName = name
} else {
    displayName = "Guest"
}

2. 合理使用三元运算符

// 好的做法 - 简单的条件选择
let height = hasHeader ? 50 : 20

// 避免 - 嵌套的三元运算符难以理解
let value = condition1 ? (condition2 ? a : b) : (condition3 ? c : d)

3. 使用区间运算符简化循环

// 好的做法 - 使用区间运算符
for i in 0..<array.count {
    print(array[i])
}

// 避免 - 传统的 C 风格循环(Swift 3 后已移除)
// for var i = 0; i < array.count; i++ {
//     print(array[i])
// }

4. 使用括号提高可读性

// 好的做法 - 使用括号明确意图
if (a && b) || (c && d) {
    // 清晰的逻辑分组
}

// 避免 - 依赖隐式优先级
if a && b || c && d {
    // 可能引起误解
}

5. 避免赋值运算符的返回值

// 好的做法 - Swift 防止了这种错误
if x == y {
    // 正确的比较
}

// 错误 - Swift 中赋值不返回值
// if x = y {
//     // 这在 Swift 中会编译错误
// }

总结

Swift 的基础运算符提供了强大而安全的值操作能力:

  • 赋值运算符不返回值,避免了常见的 === 混淆错误
  • 算术运算符默认不允许溢出,提供了更安全的数值计算
  • 比较运算符支持元组比较,提供了更灵活的比较能力
  • 空合运算符简化了可选值的处理
  • 区间运算符提供了表达范围的简洁方式
  • 逻辑运算符支持短路计算,提高了性能

理解这些运算符的行为和最佳实践,可以帮助你编写更清晰、更安全的 Swift 代码。

在 GitHub 上编辑

上次更新于