参考资料

基础运算符

运算符是用于检查、更改或组合值的特殊符号或短语。例如,加法运算符(+)将两个数字相加,如 let i = 1 + 2;逻辑与运算符(&&)组合两个布尔值,如 if enteredDoorCode && passedRetinaScan。

基础运算符

运算符是用于检查、更改或组合值的特殊符号或短语。例如,加法运算符(+)将两个数字相加,如 let i = 1 + 2;逻辑与运算符(&&)组合两个布尔值,如 if enteredDoorCode && passedRetinaScan

Swift 支持你可能已经从 C 等语言中熟悉的运算符,并改进了多项功能以消除常见的编码错误。赋值运算符(=)不返回值,以防止在本意是使用等于运算符(==)时被误用。算术运算符(+-*/% 等)会检测并禁止值溢出,以避免在处理超出存储类型允许值范围的数字时产生意外结果。你可以使用 Swift 的溢出运算符来选择启用值溢出行为,详见溢出运算符

Swift 还提供了 C 中没有的区间运算符,如 a..<ba...b,作为表达值范围的快捷方式。

本章介绍 Swift 中的常见运算符。高级运算符涵盖了 Swift 的高级运算符,并描述了如何定义自己的自定义运算符以及为自定义类型实现标准运算符。

术语

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

  • 一元运算符作用于单个目标(如 -a)。一元前缀运算符紧跟在目标之前(如 !b),一元后缀运算符紧跟在目标之后(如 c!)。
  • 二元运算符作用于两个目标(如 2 + 3),它们是中缀运算符,因为出现在两个目标之间。
  • 三元运算符作用于三个目标。与 C 一样,Swift 只有一个三元运算符,即三元条件运算符(a ? b : c)。

运算符作用的值称为操作数。在表达式 1 + 2 中,+ 符号是中缀运算符,其两个操作数是值 12

赋值运算符

赋值运算符a = b)用 b 的值初始化或更新 a 的值:

let b = 10
var a = 5
a = b
// a is now equal to 10

如果赋值的右侧是包含多个值的元组,其元素可以一次性分解为多个常量或变量:

let (x, y) = (1, 2)
// x is equal to 1, and y is equal to 2

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

if x = y {
    // This isn't valid, because x = y doesn't return a value.
}

此特性可防止在本意是使用等于运算符(==)时意外使用赋值运算符(=)。通过使 if x = y 无效,Swift 帮助你避免代码中的此类错误。

算术运算符

Swift 支持所有数字类型的四种标准算术运算符

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

与 C 和 Objective-C 中的算术运算符不同,Swift 的算术运算符默认不允许值溢出。你可以使用 Swift 的溢出运算符(如 a &+ b)选择启用值溢出行为。请参阅溢出运算符

加法运算符也支持 String 拼接:

"hello, " + "world"  // equals "hello, world"

取余运算符

取余运算符a % b)计算 b 的多少倍可以放入 a,并返回剩余的值(称为余数)。

注意 取余运算符(%)在其他语言中也称为模运算符。但是,Swift 中对负数的行为意味着,严格来说,它是取余运算而非模运算。

以下是取余运算符的工作原理。要计算 9 % 4,首先计算 9 中可以放入多少个 4

remainderInteger

你可以在 9 中放入两个 4,余数是 1(以橙色显示)。

在 Swift 中,这可以写成:

9 % 4    // equals 1

为了确定 a % b 的答案,% 运算符计算以下等式并返回 remainder 作为输出:

a = (b x some multiplier) + remainder

其中 some multiplierb 可以放入 a 的最大倍数。

94 代入这个等式得到:

9 = (4 x 2) + 1

a 为负值时,应用相同的方法计算余数:

-9 % 4   // equals -1

-94 代入等式得到:

-9 = (4 x -2) + -1

得到余数值 -1

对于 b 的负值,b 的符号会被忽略。这意味着 a % ba % -b 总是给出相同的答案。

一元负号运算符

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

let three = 3
let minusThree = -three       // minusThree equals -3
let plusThree = -minusThree   // plusThree equals 3, or "minus minus three"

一元负号运算符(-)直接放在它操作的值之前,中间没有任何空格。

一元正号运算符

一元正号运算符+)只是简单地返回它操作的值,不做任何更改:

let minusSix = -6
let alsoMinusSix = +minusSix  // alsoMinusSix equals -6

虽然一元正号运算符实际上不执行任何操作,但当你同时使用一元负号运算符处理负数时,可以使用它为正数提供代码的对称性。

复合赋值运算符

与 C 一样,Swift 提供了将赋值(=)与另一个操作组合在一起的复合赋值运算符。一个例子是加法赋值运算符+=):

var a = 1
a += 2
// a is now equal to 3

表达式 a += 2a = a + 2 的简写。实际上,加法和赋值被组合成一个同时执行两个任务的运算符。

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

有关 Swift 标准库提供的运算符的信息,请参阅运算符声明

比较运算符

Swift 支持以下比较运算符:

  • 等于(a == b
  • 不等于(a != b
  • 大于(a > b
  • 小于(a < b
  • 大于或等于(a >= b
  • 小于或等于(a <= b

注意 Swift 还提供两个恒等运算符===!==),用于测试两个对象引用是否都指向同一个对象实例。更多信息请参阅恒等运算符

每个比较运算符都返回一个 Bool 值来指示语句是否为真:

1 == 1   // true because 1 is equal to 1
2 != 1   // true because 2 isn't equal to 1
2 > 1    // true because 2 is greater than 1
1 < 2    // true because 1 is less than 2
1 >= 1   // true because 1 is greater than or equal to 1
2 <= 1   // false because 2 isn't less than or equal to 1

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

let name = "world"
if name == "world" {
    print("hello, world")
} else {
    print("I'm sorry \(name), but I don't recognize you")
}
// Prints "hello, world", because name is indeed equal to "world".

有关 if 语句的更多信息,请参阅控制流

如果两个元组具有相同的类型和相同数量的值,则可以比较它们。元组从左到右逐个值进行比较,直到比较找到两个不相等的值。这两个值进行比较,该比较的结果决定了元组比较的整体结果。如果所有元素都相等,则元组本身相等。例如:

(1, "zebra") < (2, "apple")   // true because 1 is less than 2; "zebra" and "apple" aren't compared
(3, "apple") < (3, "bird")    // true because 3 is equal to 3, and "apple" is less than "bird"
(4, "dog") == (4, "dog")      // true because 4 is equal to 4, and "dog" is equal to "dog"

在上面的例子中,你可以在第一行看到从左到右的比较行为。因为 1 小于 2,所以 (1, "zebra") 被认为小于 (2, "apple"),无论元组中的其他值如何。"zebra" 不小于 "apple" 并不重要,因为比较已经由元组的第一个元素决定了。然而,当元组的第一个元素相同时,比较它们的第二个元素——这就是第二行和第三行发生的情况。

只有当运算符可以应用于各自元组中的每个值时,才能使用给定的运算符比较元组。例如,如下面的代码所示,你可以比较两个类型为 (String, Int) 的元组,因为 StringInt 值都可以使用 < 运算符进行比较。相反,两个类型为 (String, Bool) 的元组不能用 < 运算符进行比较,因为 < 运算符不能应用于 Bool 值。

("blue", -1) < ("purple", 1)        // OK: Evaluates to true.
("blue", false) < ("purple", true)  // Error: Can't use < to compare Boolean values.

注意 Swift 标准库包含了少于七个元素的元组比较运算符。要比较七个或更多元素的元组,你必须自己实现比较运算符。

三元条件运算符

三元条件运算符是一个由三部分组成的特殊运算符,形式为 question ? answer1 : answer2。它是根据 question 是真还是假来求值两个表达式之一的快捷方式。如果 question 为真,它求值 answer1 并返回其值;否则,它求值 answer2 并返回其值。

三元条件运算符是以下代码的简写:

if question {
    answer1
} else {
    answer2
}

这里有一个例子,计算表格行的高度。如果行有标题,行高应比内容高度高 50 点;如果行没有标题,则高 20 点:

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight is equal to 90

上面的例子是以下代码的简写:

let contentHeight = 40
let hasHeader = true
let rowHeight: Int
if hasHeader {
    rowHeight = contentHeight + 50
} else {
    rowHeight = contentHeight + 20
}
// rowHeight is equal to 90

第一个例子使用三元条件运算符意味着 rowHeight 可以在单行代码中设置为正确的值,这比第二个例子中使用的代码更简洁。

三元条件运算符提供了一种高效的简写方式来决定考虑两个表达式中的哪一个。但是,请谨慎使用三元条件运算符。如果过度使用,其简洁性可能导致代码难以阅读。避免将多个三元条件运算符实例组合成一个复合语句。

空合运算符

空合运算符a ?? b)如果可选值 a 包含值则解包它,如果 anil 则返回默认值 b。表达式 a 始终是可选类型。表达式 b 必须与 a 内部存储的类型匹配。

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

a != nil ? a! : b

上面的代码使用三元条件运算符和强制解包(a!)在 a 不为 nil 时访问包装在 a 内部的值,否则返回 b。空合运算符提供了一种更优雅的方式,以简洁可读的形式封装这种条件检查和解包。

注意 如果 a 的值非 nil,则不会求值 b 的值。这称为短路求值

下面的例子使用空合运算符在默认颜色名称和可选的用户定义颜色名称之间进行选择:

let defaultColorName = "red"
var userDefinedColorName: String?   // defaults to nil

var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is nil, so colorNameToUse is set to the default of "red"

userDefinedColorName 变量被定义为可选的 String,默认值为 nil。因为 userDefinedColorName 是可选类型,你可以使用空合运算符来考虑它的值。在上面的例子中,运算符用于确定名为 colorNameToUseString 变量的初始值。因为 userDefinedColorNamenil,表达式 userDefinedColorName ?? defaultColorName 返回 defaultColorName 的值,即 "red"

如果你给 userDefinedColorName 赋一个非 nil 的值并再次执行空合运算符检查,则使用 userDefinedColorName 内部包装的值而不是默认值:

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName isn't nil, so colorNameToUse is set to "green"

区间运算符

Swift 包含几个区间运算符,它们是表达值范围的快捷方式。

闭区间运算符

闭区间运算符a...b)定义从 ab 的区间,包括值 aba 的值不能大于 b

闭区间运算符在迭代需要使用所有值的区间时很有用,例如 for-in 循环:

for index in 1...5 {
    print("\(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)定义从 ab 但不包括 b 的区间。它被称为半开是因为它包含第一个值,但不包含最终值。与闭区间运算符一样,a 的值不能大于 b。如果 a 的值等于 b,则结果区间将为空。

半开区间在处理从零开始的列表(如数组)时特别有用,在这种情况下,计数到(但不包括)列表长度很有用:

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("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(数组中最后一项的索引),因为它是半开区间。有关数组的更多信息,请参阅数组

单侧区间

闭区间运算符有一种替代形式,用于尽可能向一个方向延续的区间——例如,包含数组中从索引 2 到数组末尾的所有元素的区间。在这些情况下,你可以省略区间运算符一侧的值。这种区间称为单侧区间,因为运算符只有一侧有值。例如:

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)反转布尔值,使 true 变成 falsefalse 变成 true

逻辑非运算符是前缀运算符,紧跟在它操作的值之前,中间没有任何空格。它可以读作”非 a”,如以下例子所示:

let allowedEntry = false
if !allowedEntry {
    print("ACCESS DENIED")
}
// Prints "ACCESS DENIED".

短语 if !allowedEntry 可以读作”如果不允许进入”。只有当”不允许进入”为真时,后续行才会执行;即 allowedEntryfalse 时。

如本例所示,仔细选择布尔常量和变量名可以帮助保持代码的可读性和简洁性,同时避免双重否定或令人困惑的逻辑语句。

逻辑与运算符

逻辑与运算符a && b)创建逻辑表达式,其中两个值都必须为 true,整个表达式才为 true

如果任一值为 false,整个表达式也将为 false。事实上,如果第一个值为 false,第二个值甚至不会被求值,因为它不可能使整个表达式等于 true。这称为短路求值

这个例子考虑两个 Bool 值,只有当两个值都为 true 时才允许访问:

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

逻辑或运算符

逻辑或运算符a || b)是由两个相邻的竖线字符组成的中缀运算符。你使用它来创建逻辑表达式,其中只有一个值需要为 true,整个表达式就为 true

与上面的逻辑与运算符一样,逻辑或运算符使用短路求值来考虑其表达式。如果逻辑或表达式的左侧为 true,则不会求值右侧,因为它不能改变整个表达式的结果。

在下面的例子中,第一个 Bool 值(hasDoorKey)为 false,但第二个值(knowsOverridePassword)为 true。因为一个值为 true,整个表达式也求值为 true,允许访问:

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

组合逻辑运算符

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

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

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

如果我们输入了正确的门禁密码并通过了视网膜扫描,或者如果我们有有效的门钥匙,或者如果我们知道紧急覆盖密码,则允许访问。

根据 enteredDoorCodepassedRetinaScanhasDoorKey 的值,前两个子表达式为 false。然而,紧急覆盖密码是已知的,所以整个复合表达式仍然求值为 true

注意 Swift 逻辑运算符 &&|| 是左结合的,这意味着具有多个逻辑运算符的复合表达式首先求值最左边的子表达式。

显式括号

有时包含括号是有用的,即使它们不是严格需要的,以使复杂表达式的意图更容易阅读。在上面的门禁访问例子中,在复合表达式的第一部分周围添加括号以使其意图明确是有用的:

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

括号清楚地表明前两个值被视为整体逻辑中单独的可能状态的一部分。复合表达式的输出没有改变,但整体意图对读者来说更清晰。可读性始终优于简洁性;在有助于阐明你的意图的地方使用括号。