运算符是检查、改变、合并值的特殊符号或短语。例如,加号(+)将两个数相加(如 let i = 1 + 2)。更复杂的运算例子包括逻辑与运算符 &&(如 if enteredDoorCode && passedRetinaScan)。
Swift 支持大部分标准 C 语言的运算符,且为了减少常见编码错误做了部分改进。如:赋值符(=)不再有返回值,这样就消除了手误将判等运算符(==)写成赋值符导致代码错误的缺陷。算术运算符(+,-,*,/,% 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见 溢出运算符。
Swift 还提供了 C 语言没有的区间运算符,例如 a…<b 或 a…b,这方便我们表达一个区间内的数值。
本章节只描述了 Swift 中的基本运算符,高级运算符 这章会包含 Swift 中的高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。
术语
运算符分为一元、二元和三元运算符:
- 一元运算符对单一操作对象操作(如 -a)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如 !b),后置运算符需紧跟在操作对象之后(如 c!)。
- 二元运算符操作两个操作对象(如 2 + 3),是中置的,因为它们出现在两个操作对象之间。
- 三元运算符操作三个操作对象,和 C 语言一样,Swift 只有一个三元运算符,就是三目运算符(a ? b : c)。
受运算符影响的值叫操作数,在表达式 1 + 2 中,加号 + 是二元运算符,它的两个操作数是值 1 和 2。
赋值运算符
下表列出了 Swift 语言的基本赋值运算:
以下为赋值运算的简单实例:
var A = 10
var B = 20
var C = 100
C = A + B
print("C = A + B 结果为:\(C)")
C += A
print("C += A 结果为:\(C)")
C -= A
print("C -= A 结果为:\(C)")
C *= A
print("C *= A 结果为:\(C)")
C /= A
print("C /= A 结果为:\(C)")
上述代码输出结果为:
C = A + B 结果为:30
C += A 结果为:40
C -= A 结果为:30
C *= A 结果为:300
C /= A 结果为:30
算数运算符
基本四则算术运算符
Swift 中所有数值类型都支持了基本的四则算术运算符:
var A = 10
var B = 20
print("A + B 结果为:\(A + B)")
print("A - B 结果为:\(A - B)")
print("A * B 结果为:\(A * B)")
print("B / A 结果为:\(B / A)")
A += 1 // 类似 A++
print("A += 1 后 A 的值为 \(A)")
B -= 1 // 类似 B--
print("B -= 1 后 B 的值为 \(B)")
以上代码输出的结果为:
A + B 结果为:30
A - B 结果为:-10
A * B 结果为:200
B / A 结果为:2
A += 1 后 A 的值为 11
B -= 1 后 B 的值为 19
求余运算符
求余运算符(a %
b)是计算 b 的多少倍刚刚好可以容入 a,返回多出来的那部分(余数)。
我们来谈谈取余是怎么回事,计算 9 % 4,你先计算出 4 的多少倍会刚好可以容入 9 中:
你可以在 9 中放入2个 4,那余数是 1。在 Swift 中可以表达为:
9 % 4 // 等于 1
为了得到 a % b
的结果,%
计算了以下等式,并输出 余数作为结果:
a = (b × 倍数) + 余数
当 倍数取最大值的时候,就会刚好可以容入 a 中。把 9 和 4 代入等式中,我们得 1:9 = (4 × 2) + 1。
同样的方法,我们来计算 -9 % 4:
-9 % 4 // 等于 -1
把 -9 和 4 代入等式,-2 是使 余数 与 -9 同符号时能取到的最大整数:-9 = (4 × -2) + -1,余数是 -1。
示例:
let a = 9 % 4;
print("a: \(a)");
let b = 9 % -4;
print("b: \(b)");
let c = -9 % 4;
print("c: \(c)");
以上代码输出的结果为:
a: 1
b: 1
c: -1
一元负号运算符
数值的正负号可以使用前缀 -
(即一元负号符)来切换:
let three = 3
let minusThree = -three // minusThree 等于 -3
let plusThree = -minusThree // plusThree 等于 3, 或 "负负3"
print("three: \(three)");
print("minusThree: \(minusThree)");
print("plusThree: \(plusThree)");
上述代码输出结果:
three: 3
minusThree: -3
plusThree: 3
一元正号运算符
一元正号符(+
)不做任何改变地返回操作数的值:
let minusSix = -6
let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
print("minusSix: \(minusSix)");
print("alsoMinusSix: \(alsoMinusSix)");
上述代码输出结果:
minusSix: -6
alsoMinusSix: -6
比较运算符
以下表格列出了 Swift 语言支持的比较运算符,其中变量 A 为 10,变量 B 为 20:
每个比较运算都返回了一个标识表达式是否成立的布尔值:
let a = 1 == 1 // true, 因为 1 等于 1
let b = 2 != 1 // true, 因为 2 不等于 1
let c = 2 > 1 // true, 因为 2 大于 1
let d = 1 < 2 // true, 因为 1 小于2
let e = 1 >= 1 // true, 因为 1 大于等于 1
let f = 2 <= 1 // false, 因为 2 并不小于等于 1
print("type of a: \(type(of: a)), a value: \(a)");
print("type of b: \(type(of: b)), a value: \(b)");
print("type of c: \(type(of: c)), a value: \(c)");
print("type of d: \(type(of: d)), a value: \(d)");
print("type of e: \(type(of: e)), a value: \(e)");
print("type of f: \(type(of: f)), a value: \(f)");
上述代码输出结果为:
type of a: Bool, a value: true
type of b: Bool, a value: true
type of c: Bool, a value: true
type of d: Bool, a value: true
type of e: Bool, a value: true
type of f: Bool, a value: false
如果两个元组的元素相同,且长度相同的话,元组就可以被比较。比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如:
let aa = (1, "zebra") < (2, "apple") // true,因为 1 小于 2
let bb = (3, "apple") < (3, "bird") // true,因为 3 等于 3,但是 apple 小于 bird
let cc = (4, "dog") == (4, "dog") // true,因为 4 等于 4,dog 等于 dog
print("type of aa: \(type(of: aa)), a value: \(aa)");
print("type of bb: \(type(of: bb)), a value: \(bb)");
print("type of cc: \(type(of: cc)), a value: \(cc)");
上述代码输出结果:
type of aa: Bool, a value: true
type of bb: Bool, a value: true
type of cc: Bool, a value: true
当元组中的元素都可以被比较时,你也可以使用这些运算符来比较它们的大小。例如,像下面展示的代码,你可以比较两个类型为 (String, Int) 的元组,因为 Int 和 String 类型的值可以比较。相反,Bool
不能被比较,也意味着存有布尔类型的元组不能被比较。
("blue", -1) < ("purple", 1) // 正常,比较的结果为 true
("blue", false) < ("purple", true) // 错误,因为 < 不能比较布尔类型
三元运算符
三元运算符的特殊在于它是有三个操作数的运算符,它的形式是 问题 ? 答案 1 : 答案 2
。它简洁地表达根据 问题成立与否作出二选一的操作。如果 问题
成立,返回 答案 1
的结果;反之返回 答案 2
的结果。
三元运算符是以下代码的缩写形式:
if question {
answer1
} else {
answer2
}
这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点:
let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20) // rowHeight 现在是 90
print("rowHeight:\(rowHeight)");
上述代码输出的结果是:
rowHeight:90
上面的写法比下面的代码更简洁:
let contentHeight = 40
let hasHeader = true
var rowHeight = contentHeight
if hasHeader {
rowHeight = rowHeight + 50
} else {
rowHeight = rowHeight + 20
}
// rowHeight 现在是 90
第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 rowHeight 定义成变量,因为它的值无需在 if 语句中改变。
三元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,滥用三元运算符会降低代码可读性。所以我们应避免在一个复合语句中使用多个三元运算符。
空合运算符
空合运算符(a ?? b
)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解包,否则就返回一个默认值 b。表达式 a 必须是 Optional
类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。
空合运算符是对以下代码的简短表达方法:
a != nil ? a! : b
上述代码使用了三元运算符。当可选类型 a 的值不为空时,进行强制解包(a!
),访问 a 中的值;反之返回默认值 b。无疑空合运算符(??
)提供了一种更为优雅的方式去封装条件判断和解包两种行为,显得简洁以及更具可读性。
下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择:
let defaultColorName = "red"
var userDefinedColorName: String? //默认值为 nil
var colorNameToUse = userDefinedColorName ?? defaultColorName // userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red"
print("colorNameToUse 值为: \(colorNameToUse)")
上述代码输出的结果为:
colorNameToUse 值为: red
userDefinedColorName 变量被定义为一个可选的 String 类型,默认值为 nil。由于 userDefinedColorName 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 colorNameToUse 的变量赋予一个字符串类型初始值。 由于 userDefinedColorName 值为空,因此表达式 userDefinedColorName ?? defaultColorName 返回 defaultColorName 的值,即 red。
如果你分配一个非空值(non-nil)给 userDefinedColorName,再次执行空合运算,运算结果为封包在 userDefinedColorName 中的值,而非默认值。
userDefinedColorName = String()
userDefinedColorName = "yellow"
colorNameToUse = userDefinedColorName ?? defaultColorName
print("colorNameToUse 值为: \(colorNameToUse)") // userDefinedColorName 的值不为空,所以 colorNameToUse 的值为 "yellow"
上述代码输出的结果为:
colorNameToUse 值为: yellow
区间运算符
闭区间运算符
闭区间运算符(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 的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。
半开区间的实用性在于当你使用一个从 0 开始的列表(如数组)时,非常方便地从0数到列表的长度。
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
单侧区间运算符
闭区间操作符有另一个表达形式,可以表达往一侧无限延伸的区间 —— 例如,一个包含了数组从索引 2 到结尾的所有值的区间。在这些情况下,你可以省略掉区间操作符一侧的值。这种区间叫做单侧区间,因为操作符只有一侧有值。例如:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names[2...] {
print("[2...] -- \(name)");
}
for name in names[...2] {
print("[...2] -- \(name)");
}
上述代码输出结果为:
[2...] -- Brian
[2...] -- Jack
[...2] -- Anna
[...2] -- Alex
[...2] -- Brian
半开区间操作符也有单侧表达形式,附带上它的最终值。就像你使用区间去包含一个值,最终值并不会落在区间内。例如:
for name in names[..<2] {
print("[..<2] -- \(name)");
}
上述代码输出结果为:
[..<2] -- Anna
[..<2] -- Alex
单侧区间不止可以在下标里使用,也可以在别的情境下使用。你不能遍历省略了初始值的单侧区间,因为遍历的开端并不明显。你可以遍历一个省略最终值的单侧区间;然而,由于这种区间无限延伸的特性,请保证你在循环里有一个结束循环的分支。你也可以查看一个单侧区间是否包含某个特定的值,就像下面展示的那样:
let range = ...5
let result1 = range.contains(7) // false
let result2 = range.contains(4) // true
let result3 = range.contains(-1) // true
print("result1: \(result1)")
print("result2: \(result2)")
print("result3: \(result3)")
上述代码输出结果为:
result1: false
result2: true
result3: true
逻辑运算符
逻辑运算符的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。
- 逻辑非(
!
a) - 逻辑与(a
&&
b) - 逻辑或(a
||
b)
逻辑非运算符
逻辑非运算符(!
a)对一个布尔值取反,使得 true
变 false
,false
变 true
。
它是一个前置运算符,需紧跟在操作数之前,且不加空格。读作 非 a ,例子如下: