操作符是用于检测、更改或者组合值的特殊符号或短语。例如,加法操作符 (+) 将两个数字加到一起 (如 let i = 1 + 2)。更复杂的例子包括逻辑与操作符 && (如 if enteredDoorCode && passedRetinaScan) 和自增操作符 ++i,就是把 i 的值加1的快捷方式。
Swift 支持大多数标准的C 操作符并且改善了几项功能以消除常见的编码错误。赋值操作符 (=) 不返回值,是为了防止想用等于操作符(==)时不小心用成了赋值操作符。算数运算符 (+,-,*,/,% 等等) 检测并禁止值溢出,以免运算时得出的结果大于或小于类型允许的范围。你可以用 Swift 的溢出运算符来做溢出操作,详情见《溢出操作符》一节。
不像C,Swift 可以对浮点数进行取余操作 (%) 。 Swift 还提供两个范围操作符 (a..<
b 和 a…b) ,这在C里是没有的,用来表示一个范围的值。
这一章讲述 Swift 里的常用操作符。《高级操作符》一节涵盖了Swift 的高级操作符,讲述了如何自定义操作符以及如何实现自定义类型的标准操作符。
术语
操作符分一元、二元和三元:
一元操作符操作于一个单一目标(如 -a)。一元前缀操作符在它们的目标之前紧跟出现(如 !b),一元后缀操作符在它们的目标之后紧跟出现(如 i++)。
二元操作符操作于两个目标(如 2 + 3),是中缀操作符,因为它出现在两个目标的中间。
三元操作符操作于三个目标。像 C 一样,Swift 只有一个三元操作符,就是三元条件操作符 (a ? b : c)。
操作符影响的值称为操作数。在表达式 1 + 2 中,符号 + 是一个二元操作符,它的操作数是值 1 和 2。
赋值操作符
赋值操作符 (a = b) 用 b 的值初始化或更新 a 的值:
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 帮你避免 if 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"
取余操作符
取余操作符 (a % b) 计算的是 a 是 b 的几个倍数,并返回剩余的部分 (就是余数)。
注意
取余操作符 (%) 在其它语言里也称为取模操作符。不过,在Swift 里对负数的操作,严格来说是取余而不是取模。
这是取余操作符如何工作的例子。要计算 9 % 4,先算出9最多可以包括多少个4:
9里最多有两个4,余数是1 (上面的橙色部分).
Swift 里,这个可以写作:
9 % 4 // 等于 1
要得出 a % b 的答案, % 操作符计算以下等式并返回 remainder 作为它的输出:
a = (b × some multiplier) + remainder
some multiplier 是 a 所能容纳的b的最大倍数。
把9和4插入这个等式得出:
9 = (4 × 2) + 1
计算负数a的值也是一样的方法:
-9 % 4 // 等于 -1
把 -9 和 4 插入等式得出:
-9 = (4 × -2) + -1
得出值为 -1。
b 的负数值负号会被忽略掉。这就是说 a % b 和 a % -b 都是得出一样的答案。
浮点取余计算
不像 C 和 Objective-C 的取余操作符,Swift 的取余操作符还可以计算浮点数:
8 % 2.5 // equals 0.5
在这个例子里,8 除以 2.5 等于 3,余数为 0.5,因此取余操作符返回一个 Double 值 0.5。
自增与自减操作符
像 C 一样, Swift 提供了一个自增操作符 (++) 和一个自减操作符 (- -) 表示把一个数字变量加1或减1。你可以把这两个操作符用于任何整型或浮点型变量。
var i = 0
++i // i 现在等于 1
每一次调用 ++i,i 的值都会加1。本质上来说, ++i 是 i = i + 1 的缩写。同样, - -i 是 i = i - 1的缩写。
++ 和 - - 符号既可以用于前缀,也可以用于后缀。 ++i 和 i++ 都可以把 i 的值增加1。同样, - -i 和 i- - 都可以把 i 的值减1。
注意这两个操作符在修改i的同时也会返回一个值。如果你仅仅是想增加或减少 i 的值,可以不管返回值。不过,如果确实要用返回值,前缀和后缀的使用就不一样了,根据以下规则:
如果操作符写在变量前,那么先加1或减1再返回值。
如果操作符写在变量后,那么先返回值再加1或减1。
例如:
var a = 0
let b = ++a
// a 和 b 现在都等于 1
let c = a++
// a 现在等于 2,但 c 被设为加之前的值 1
在上例中, let b = ++a 在返回值前增加 a 的值。这就是为什么 a 和 b 都等于新的值 1。
但是,let c = a++ 在返回值以后再增加 a 的值。这就是说 c 得到了旧的值 1,而 a 更改为等于2。
除非需要特别使用 i++,否则推荐你都用 ++i 和 –i ,因为这个更符合修改 i 和返回结果的典型预期行为。
一元减操作符
数字值的符号可以用前缀 - 进行切换,也就是一元减操作符:
let three = 3
let minusThree = -three // minusThree 等于 -3
let plusThree = -minusThree // plusThree 等于 3,或者 "负 负 三"
一元减操作符 (-) 直接加在要操作的值之前,不加任何空格。
一元加操作符
一元加操作符 (+) 只是简单地返回所操作的值,不作任何改变:
let minusSix = -6
let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
虽然一元加操作符实际上不干任何事,但是在既有负数又有正数的时候可以让你的代码看起来更对称一点。
复合赋值操作符
像 C 那样,Swift 也提供复合赋值操作符,将复制符 (=) 和其它操作结合起来。一个例子就是加赋值操作符 (+=):
var a = 1
a += 2 // a 现在等于 3
表达式 a += 2 是 a = a + 2 的缩写。加和赋值结合到一个操作符可以同时执行两个操作。
注意
复合操作符不返回值。不能这么写 let b = a += 2。这个和上面提到的加和减操作符不同。
《表达式》一节里有全部的复合赋值操作符介绍。
比较操作符
Swift 支持所有标准的 C 比较操作符:
等于 (a == b)
不等于 (a != b)
大于 (a > b)
小于 (a < b)
大于或等于 (a >= b)
小于或等于 (a <= b)
注意
Swift 还提供了两个恒等操作符 (=== 和 !==),用来测试两个对象引用是否都引用同一个对象实例。更多信息,请参考《类与结构体》一章。
每一个比较操作符都返回一个布尔值,指示语句是否为true:
1 == 1 // true,因为 1 等于 1
2 != 1 // true,因为 2 不等于 1
2 > 1 // true,因为 2 大于 1
1 < 2 // true,因为 1 小于 2
1 >= 1 // true,因为 1 大于或等于 1
2 <= 1 // false,因为 2 不小于或等于 1
比较操作符通常用在条件语句,比如 if 语句里:
let name = "world"
if name == "world" {
println("hello,world")
} else {
println("I'm sorry \(name),but I don't recognize you")
}
// 打印出 "hello,world",because name is indeed equal to "world"
要了解更多关于 if 语句的信息,参见《控制流》一章。
三元条件操作符
三元条件操作符分三个部分,形式是这样的 question ? answer1 : answer2。根据 question 是 true 还是 false 来返回那两个表达式中的一个。如果 question 是 true,返回 answer1 ;否则,返回 answer2 。
三元条件操作符是以下代码的简写:
if question { answer1 } else { answer2 }
这是一个计算表格行高的例子。如果行有表头的话,行高应该比内容的高度高50个点,如果没有表头,高20个点:
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
第一个例子里,三元条件操作符的使用表示 rowHeight 可以在一行代码里就设置上正确的值。这比第二个例子简洁得多,而且不需要把 rowHeight 定义为变量,因为它的值没有必要在 if 语句里进行更改。
三元条件操作符为决定使用两个表达式中哪一个提供了高效的缩写。但是,使用三元条件操作符的时候要小心。滥用的话,虽然简洁,但会让你的代码不好读懂。尽量避免将多个三元条件操作符合并到一条复合语句中。
Nil 合并操作符
nil 合并操作符 (a ?? b) 如果a有值则开启可选项a,否则如果a是 nil 返回 b 的值。表达式 a 都是可选类型。表达式 b 必须匹配a里存储的类型。
nil 合并运算符是以下例子的缩写:
a != nil ? a! : b
上例使用三元条件操作符并强制开启 (a!) 并当a不为nil时访问a里的值,否则返回b。 nil 合并操作符提供了封装这个条件检测和开启的一种更高雅的方式,而且形式更简洁和可读性更高。
注意
如果a的值是非nil的,b不会被求值。这就是所谓的短路求值。
以下的例子使用 nil 合并操作符从默认颜色名和可选的用户自定义颜色名之间做选择:
let defaultColorName = "red"
var userDefinedColorName: String? // 默认为 nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 是 nil,所以 colorNameToUse 被设置为默认的 "red"
userDefinedColorName 变量被定义为一个可选的 String ,默认值为 nil 。因为 userDefinedColorName 是一个可选类型,你可以用 nil 合并操作符来得到它的值。在上例中,操作符用来检测名为 colorNameToUse 的 String 的初始值。由于 userDefinedColorName 是 nil,所以表达式 userDefinedColorName ?? defaultColorName 返回 defaultColorName 的值 “red”。
如果你把一个非nil的值设置给 userDefinedColorName 并执行 nil 合并操作符再次检测的话,那么 userDefinedColorName 里的值就会被使用,而不是默认的那个了:
userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 不是 nil,所以 colorNameToUse 被设置为 "green"
范围操作符
Swift 包括两个范围操作符,表示一个范围的值的缩写。
闭合范围操作符
闭合范围操作符 (a…b) 定义了一个从 a 到 b 的范围,同时包括a 和 b 的值。a 的值必须不能大于 b 。
闭合范围操作符适合用于遍历一个范围里所有的值都要拿来用的场合,如 for-in 循环:
for index in 1...5 {
println("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
更多关于 for-in 循环,参见《控制流》一章。
半开范围操作符
半开范围操作符 (a..<b
) 定义了一个从a到b的范围,但是不包括b。因为只包括第一个值,不包括最后那个值,所以称为半开。和闭合范围操作符一样,a的值不能大于b。
半开范围特别适用于从0开始的列表,如数组,一直数到(但不包括)列表的长度:
let names = ["Anna","Alex","Brian","Jack"]
let count = names.count
for i in 0..<count {
println("Person \(i + 1) is called \(names[i])")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack
注意数组包含了四个项,不过 0..<count
只数到3 (就是数组的最后一个索引),因为它是一个半开范围。关于数组的更多信息,参见《数组》一节。
逻辑操作符
逻辑操作符用于修改或合并布尔逻辑值true和false。Swift 支持基于C语言的三种标准的逻辑操作符:
逻辑否 (!a)
逻辑与 (a && b)
逻辑或 (a || b)
逻辑否操作符
逻辑否操作符 (!a) 将一个布尔值取反操作,true变false,false变true。
逻辑否操作符是一个前缀操作符,在其操作的值之前立即出现,没有空格。它可以读作 “不是 a”,如下例所示:
let allowedEntry = false
if !allowedEntry {
println("ACCESS DENIED")
}
// 打印出 "ACCESS DENIED"
短语 if !allowedEntry 可以读作 “如果不允许进入” 。如果“不允许进入”是true,后续代码才会执行;也就是如果 allowedEntry 是 false。
正如在这个例子里所说,谨慎使用布尔常量和变量名可以帮助保持代码的可读性和简洁性,而避免双重否定或混乱的逻辑语句。
逻辑与操作符
逻辑与操作符 (a && b) 创建逻辑表达式,只有两个值都是true时整个表达式的值才为true。
如果其中一个值是false,整个表达式也会是false。实际上,第一个值是false的话,都不会再看第二个值,因为不可能会使整个表达式等于true的。这就是所谓的短路取值。
这个例子检测两个Bool 值,并且只有两个值都是true的时候才允许访问:
let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
println("Welcome!")
} else {
println("ACCESS DENIED")
}
// 打印出 "ACCESS DENIED"
逻辑或操作
逻辑或操作 (a || b) 是一个中缀操作符,位于两个相邻的字符之间。用它来创建的表达式只要其中一个值为true,那整个表达式的值都为true。
如上面的逻辑与操作符一样,逻辑或操作符也使用短路取值。如果逻辑与左边的表达式是true,右边的部分就不用看了,因为不管怎样都不会改变整个表达式的输出。
在下面的例子中,第一个Bool值I (hasDoorKey) 是 false,不过第二个值 (knowsOverridePassword) 是 true。由于其中一个值是true,所以整个表达式的值就是true,允许访问:
let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
println("Welcome!")
} else {
println("ACCESS DENIED")
}
// 打印出 "Welcome!"
逻辑操作符的结合
你可以将多个逻辑操作符结合起来以创建更长的复合表达式:
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
println("Welcome!")
} else {
println("ACCESS DENIED")
}
// 打印出 "Welcome!"
这个例子使用了多个 && 和 || 操作符来创建一个更长的复合表达式。然而, && 和 || 操作符仍然只操作与两个值,所以这其实是三个更小的表达式串在一起。这个例子可以读作:
如果我们进入了正确的门并通过了视网膜扫描,或者我们有一把有效的门钥匙,或者我们知道紧急替代密码,那就可以允许访问。
基于 enteredDoorCode,passedRetinaScan,和 hasDoorKey 的值,头两个小表达式是false。不过,知道了紧急替代密码,所以整个复合表达式仍然为true。
显示的括号
有时候加上括号也是很有用的,虽然比不是严格要求必须要有,但可以使复杂的表达式更容易读懂。在上面的门禁例子里,在复合表达式的第一部分加上括号就很有用了:
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
println("Welcome!")
} else {
println("ACCESS DENIED")
}
// 打印出 "Welcome!"
加了括号就很明显的能看出前两个值是作为整个逻辑的一个独立部分的。复合表达式的输出并没改变,但整个表达式的意图更容易读懂了。相比简洁性,可读性更受青睐;用括号就可以让你代码的意思更明了。