基础运算符
理解 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 % b 和 a % -b 的结果是相同的。
9 % -4 // 等于 1
-9 % -4 // 等于 -1组合赋值运算符
Swift 提供了组合赋值运算符,将赋值运算符 = 与其他运算符组合。
var a = 1
a += 2
// a 现在等于 3表达式 a += 2 是 a = 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) 的元组,因为 String 和 Int 都可以比较。相反,两个类型为 (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 定义一个包含从 a 到 b(包括 a 和 b)的所有值的区间。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 定义一个从 a 到 b 但不包括 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逻辑运算符
逻辑运算符修改或组合布尔逻辑值 true 和 false。Swift 支持基于 C 语言的三个标准逻辑运算符:
- 逻辑非
!a - 逻辑与
a && b - 逻辑或
a || b
逻辑非运算符
逻辑非运算符 !a 对一个布尔值取反,使得 true 变 false,false 变 true。
逻辑非运算符是一个前缀运算符,直接出现在操作值之前,不加空格。
let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
// 输出 "ACCESS DENIED"if !allowedEntry 可以读作"如果不允许进入"。只有在"不允许进入"为 true 时,即 allowedEntry 为 false 时,该行才会被执行。
逻辑与运算符
逻辑与运算符 a && b 表达了只有 a 和 b 的值都为 true 时,整个表达式的值才会是 true。
只要任意一个值为 false,整个表达式的值就为 false。事实上,如果第一个值为 false,那么第二个值不会被计算,因为它已经不可能影响整个表达式的结果了。这被称为短路计算。
let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出 "ACCESS DENIED"逻辑或运算符
逻辑或运算符 a || b 是一个由两个连续的 | 组成的中缀运算符。它表示了只要 a 或 b 其中一个为 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 代码。
上次更新于