Skip to content

基本运算符

运算符是用于检查、修改或合并值的特殊符号。例如,加号( + )用于将两个数字相加(如:let i = 1 + 2 )。

更复杂的运算示例包括逻辑与运算符( && ),它在两个条件都满足时返回(例如:if enteredDoorCode && passedRetinaScan)。

Swift 支持的许多运算符在其他语言中也存在,比如 C 语言,但 Swift 对这些运算符进行了改进,以减少常见的编程错误。例如,赋值运算符( = )不再返回任何值,这样可以避免由于将等于运算符( == )误写为赋值运算符而引发的代码错误。

算术运算符(如 +-*/% )会进行值溢出检查,防止因变量超出其数据类型所能承载的范围而产生异常结果。

溢出运算符

Swift 也允许使用专门的溢出运算符来处理值溢出。

此外,Swift 引入了一些 C 语言中没有的区间运算符,如 a..<ba...b,这使得表示数字范围变得更加方便。

本章节仅介绍了 Swift 中的基本运算符。在高级运算符一章中,我们将探讨 Swift 中的高级运算符,包括如何自定义运算符以及如何对自定义类型进行运算符重载。

术语

运算符根据它们操作的对象数量可以分为一元二元三元运算符:

一元运算符作用于一个操作对象。例如,负号运算符( -a )是一元运算符。一元运算符又分为前置后置运算符:前置运算符位于操作对象之前(如 !b ),而后置运算符位于操作对象之后(如 c! )。

二元运算符作用于两个操作对象,并位于这两个操作对象之间,因此被称为中置运算符。一个典型的例子是加法运算符,如在表达式 2 + 3 中。

三元运算符涉及三个操作对象。在 Swift 和 C 语言中,唯一的三元运算符是三目运算符,格式为 a ? b : c,用于根据条件表达式 a 的真假值来选择 bc

赋值运算符

赋值运算符( a = b )代表将变量 b 的值分配给变量 a。例如:

swift
let b = 10
var a = 5
a = b
// 现在 a 的值是 10

当赋值操作的右侧是一个元组时,你可以同时将元组的元素解构到多个常量或变量中:

swift
let (x, y) = (1, 2)
// x 的值现在是 1,y 的值现在是 2

与 C 语言和 Objective-C 不同,Swift 的赋值语句不会返回任何值。因此,以下的 if 语句在 Swift 中是无效的:

swift
if x = y { 
    // 此语句错误,因为 `x = y` 不返回任何值
}

注意

这种设计防止了将等于运算符(==)误写为赋值运算符(=)的常见错误。

算术运算符

Swift 中所有数值类型都支持了基本的四则算术运算符:

加法( + ) 减法( - ) 乘法( * ) 除法( /

swift
1 + 2       // 等于 3
5 - 3       // 等于 2
2 * 3       // 等于 6
10.0 / 2.5  // 等于 4.0

与 C 语言和 Objective-C 不同的是,Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如 a &+ b )。

加法运算符也可用于字符串( String )的拼接:

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

求余运算符

求余运算符( a % b ),也称为模运算符,用于计算 a 除以 b 后的余数。具体来说,它找到 b 能在 a 中完全容纳多少次,然后返回剩余的部分。

「求余」和「取模」

在某些情况下,求余运算符的行为与严格的取模运算略有区别,因此将其称为「求余」可能更准确。这种差异体现在当参与运算的数是负数时,不同语言或环境下运算结果可能不同。

  1. 求余运算

    • 求余运算( a % b )在大多数编程语言中定义为:它返回除法 a / b 后的余数。
    • 无论 a 是正数还是负数,余数的符号通常与 a 相同
    • 例如,在 Python 和 JavaScript 中,-10 % 3 会返回 -1,因为 -10 除以 3 得到 -3-1
  2. 取模运算

    • 取模运算与求余运算类似,但它要求余数的符号总是非负的,即使被除数是负数也是如此。
    • 举个例子,在某些数学定义中,-10 mod 3 应返回 2,因为最接近 -10 的、由 3 的倍数组成的数是 -12(而不是 -9),所以 -10 - (-12) 等于 2

这种差异主要是因为不同编程语言和数学规则对这两种运算的定义不完全相同。在编程实践中,这意味着当你在使用这些运算符时,需要注意你的编程环境是如何定义它们的,特别是在涉及负数时。如果你在一个特定的环境中工作(如 Swift、Python、Java),最好查看该环境的文档来确认使用的是哪种方式,从而确保你的计算结果符合预期。

这里有几个简单的例子:

示例 1:基本求余

swift
let a = 26
let b = 5
let result = a % b
// 输出: result is 1

在这个例子中,26 除以 5 等于 51,所以结果是 1

示例 2:负数的求余

swift
let c = -26
let d = 5
let negativeResult = c % d
// 输出: negativeResult is -1

这里,26 除以 551。余数的符号与被除数相同。

示例 3:被除数为负数

swift
let e = 26
let f = -5
let mixedResult = e % f
// 输出: mixedResult is 1

在这种情况下,26 除以 5 也是 51。同样,余数的符号由被除数决定,因此结果为正 1

示例 4:除数和被除数均为负数

swift
let g = -26
let h = -5
let bothNegativeResult = g % h
// 输出: bothNegativeResult is -1

这里,26 除以 551,余数符号与被除数相同。

一元负号运算符

数值的正负号可以使用前缀 - 来切换:

swift
let three = 3
let minusThree = -three       // minusThree 等于 -3
let plusThree = -minusThree   // plusThree 等于 3, 或 "负负3"

一元负号符( - )写在操作数之前,中间没有空格

一元正号运算符

一元正号符( + )不做任何改变地返回操作数的值:

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

组合赋值运算符

像 C 语言一样,Swift 也支持将赋值运算符( = )与其他运算符结合使用的组合赋值运算符。一个常见的例子( += ),这个运算符将右侧的值到左侧的变量上,并将结果重新赋值给左侧的变量。例如:

swift
var a = 10
a += 5
// 现在 a 的值是 15

在这个例子中,a += 5 相当于 a = a + 5。使用 += 运算符,我们可以更简洁地表达「增加并赋值」的操作。

比较运算符

Swift 支持以下的比较运算符:

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

每个比较运算都返回了一个 Bool 表达式:

swift
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 语句:

swift
let name = "world"
if name == "world" {
    print("hello, world")
} else {
    print("I'm sorry \(name), but I don't recognize you")
}
// 输出“hello, world", 因为 `name` 就是等于 "world”
比较元组

如果两个元组的元素类型和数量相同,这些元组就可以进行比较。比较两个元组时会从左到右逐个比较它们的元素。只有当前面的元素相等时,才会比较后面的元素。这意味着,整个元组的比较结果可能由任一元素决定,取决于从左到右的第一个不相等的元素。例如:

swift
(1, "zebra") < (2, "apple")   // 结果为 true,因为 1 小于 2
(3, "apple") < (3, "bird")    // 结果为 true,因为尽管第一个元素相等,第二个元素 "apple" 小于 "bird"
(4, "dog") == (4, "dog")      // 结果为 true,所有元素都相等

能比较的数据类型,如 IntString。然而,不所有类型都可以使用比较运算符,例如 Bool 类型,因为布尔值不支持比较大小。

swift
("blue", -1) < ("purple", 1)       // 结果为 true,因为字符串 "blue" 小于 "purple"
("blue", false) < ("purple", true) // 错误,因为布尔类型不能使用小于运算符

需要注意的是,Swift 标准库仅支持对元素数量不超过七个的元组进行比较。如果元组包含超过七个元素,你将需要自定义比较逻辑。

三元运算符

三元运算符是一个涉及三个操作数的运算符。这个运算符提供了一种简洁的方式来根据条件选择两个表达式之一。如果条件为,则运算符返回 表达式 1 的结果;如果条件为,则返回 表达式2 的结果。

三元运算符是一种简化版的条件语句,其形式为 question ? answer1 : answer2。这实际上是以下 if-else 语句的缩写:

swift
if question {
    answer1
} else {
    answer2
}

假设我们正在编写一个程序来显示用户的状态消息。用户可以设置状态为「在线」或「离线」,并且我们希望在界面上相应地显示不同的消息颜色。

swift
var isOnline = true
let statusColor = isOnline ? "Green" : "Red"
// statusColor 将是 "Green"

在这个例子中,isOnline 是一个布尔变量,用于表示用户是否在线。使用三元运算符,我们可以在一行内决定 statusColor 应该是绿色(表示在线)还是红色(表示离线)。

这种方式非常适合在需要根据条件快速选择两个选项之一的场景,极大地简化了代码,避免了更冗长的 if-else 语句结构。

注意

滥用三元运算符会降低代码可读性,所以我们应避免在一个复合语句中使用多个三元运算符。

空合运算符

空合运算符( a ?? b )是一种用于处理「可选类型」的运算符,它提供了一种简洁的方法来处理可选值。

如果可选类型 a 包含一个值,该运算符会解包 a 并返回这个值;如果 a 为空(即 nil ),则返回默认值 b

此外,表达式 a 必须是一个可选类型,而默认值 b 的「类型」必须与 a 中潜在值的类型相「一致」。

空合运算符是以下三元运算符表达式的简洁替代方式:

swift
a != nil ? a! : b
  • 如果可选类型 a 有值,则使用 ! 进行强制解包以访问该值;
  • 如果 a 为空,则返回默认值 b

假设我们正在开发一个应用,其中用户的昵称是可选的。如果用户没有提供昵称,我们希望在界面上显示一个默认的昵称。

swift
// 可选类型的昵称
var nickname: String? = nil

// 使用空合运算符提供默认昵称
let displayName = nickname ?? "Guest"

// 输出结果
print("Welcome, \(displayName) !!!")
  • nickname是一个可选的 String 类型,最初被设置为 nil,表示没有值。
  • 我们使用空合运算符 ?? 来决定默认显示的名称。由于nicknamenil,所以空合运算符会返回值 Guest

如果我们更改 nickname 的值为非空并再次运行相同的代码,例如:

swift
nickname = "Alice"
let displayName = nickname ?? "Guest"
print("Welcome, \(displayName) !!!")
  • 由于nicknameAlice,所以空合运算符会返回值 Alice

区间运算符

闭区间运算符

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

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

swift
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 的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。例如:

swift
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 到 无穷() ,你可以省略区间操作符的一侧值:

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

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

半开区间操作符也可以用于创建单侧区间,这时区间只有一个明确的起始点或结束点,而另一端则是无限延伸的的()。例如:

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

注意

你无法遍历省略了起始值的单侧区间(如...5),因为它没有明确的开始点。然而,你可以遍历一个省略了结束值的单侧区间(如5...),但由于这种区间理论上可以无限延伸,所以在使用时需要确保循环中有终止条件。

此外,单侧区间也可以用来检查是否包含某个特定值。例如:

swift
let range = ...5
range.contains(7)   // 返回 false,因为 7 不在区间 ...5 内
range.contains(4)   // 返回 true,因为 4 包含在区间 ...5 内
range.contains(-1)  // 返回 true,因为 -1 也包含在区间 ...5 内

在这个例子中,区间 ...5 表示从负无穷大()到 5 的所有数(包括 5 ),因此,range.contains(4)range.contains(-1) 返回 true,而 range.contains(7) 返回 false。非常适用于快速判断一个值是否属于某个范围。

逻辑运算符

逻辑运算符的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。

逻辑非( !a

逻辑与( a && b

逻辑或( a || b

逻辑非

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

它是一个前置运算符,需放在操作数之前,且不加空格。读作 非a,例子如下:

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

逻辑与

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

只要任意一个值为 false,整个表达式的值就为 false(短路)。

以下例子,只有两个 Bool 值都为 true 的时候才允许进入if

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

逻辑或

逻辑或运算符( a || b )是一个中置运算符,由两个连续的竖线( || )组成。它用来连接两个逻辑表达式,如果其中至少一个表达式为 true,那么整个表达式的结果就为true(短路)。

swift
var accessGranted = false
let isAdmin = true
let hasValidPassword = false

accessGranted = isAdmin || hasValidPassword

print("Access granted: \(accessGranted)")

逻辑运算符组合计算

我们可以组合多个逻辑运算符来表达一个复合逻辑:

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

这里,表达式的逻辑条件如下:

  1. enteredDoorCode && passedRetinaScan : 这部分是一个逻辑与运算。要求两个条件同时满足:用户必须输入正确的门禁码 (enteredDoorCode) 并且通过视网膜扫描验证 (passedRetinaScan)。

  2. hasDoorKey: 这是一个简单的条件,检查用户是否持有门钥匙。

  3. knowsOverridePassword: 这也是一个简单的条件,检查用户是否知道可以覆盖常规安全措施的密码。

整体使用了逻辑或运算符 ( || ) 连接上述三个条件。因此,如果用户符合以下任一条件:

  • 同时满足输入正确的门禁码并通过视网膜扫描
  • 持有门钥匙
  • 知道可以覆盖安全系统的密码

则执行 print("Welcome!") 表示欢迎进入。否则,如果这三个条件都不满足,执行 else 分支,打印 ACCESS DENIED,表示访问被拒绝。

左结合

逻辑操作符 && (逻辑与) 和 || (逻辑或) 是左结合的,这意味着当表达式中包含多个这样的操作符时,解析器会从左向右处理它们,优先计算最左边的子表达式。

使用括号来明确优先级

为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,我们给第一个部分加个括号,使它看起来逻辑更明确:

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