Swift 运算符
运算符是一个符号,用来告诉编译器对操作数执行某种运算,例如相加、比较大小或做逻辑判断。
Swift 的运算符大体可以分为算术、溢出、比较、逻辑、位运算、区间和空合等几类。
运算符分类
下表按用途对 Swift 的常用运算符做了归类,后面各节会逐类展开。
| 类别 | 运算符 |
|---|---|
| 算术 | + - * / % |
| 溢出 | &+ &- &* |
| 比较 | == != < <= > >= === !== |
| 逻辑 | && || ! |
| 位运算 | & | ^ ~ << >> &<< &>> |
| 区间 | a...b a..<b ...b a... |
| 空合 | ?? |
| 三元 | 条件 ? 值1 : 值2 |
算术运算符
算术运算符用于加减乘除和求余,下表中的 A 为 10,B 为 20。
| 运算符 | 描述 | 示例 |
|---|---|---|
+ | 加 | A + B 结果为 30 |
- | 减 | A - B 结果为 -10 |
* | 乘 | A * B 结果为 200 |
/ | 除 | B / A 结果为 2 |
% | 求余 | B % A 结果为 0 |
实例
let B = 20
print("A + B 结果为:\(A + B)")
print("A - B 结果为:\(A - B)")
print("A * B 结果为:\(A * B)")
print("B / A 结果为:\(B / A)")
print("B % A 结果为:\(B % A)")
以上程序执行结果为:
A + B 结果为:30 A - B 结果为:-10 A * B 结果为:200 B / A 结果为:2 B % A 结果为:0
整数除法会截断小数
两个整数相除,结果仍然是整数,小数部分被直接丢掉(向零截断),余数的符号跟被除数一致。
实例
print("(-7) / 2 = \((-7) / 2)")
print("7 % 3 = \(7 % 3)")
print("(-7) % 3 = \((-7) % 3)")
// 想得到小数结果,至少把一边写成浮点数
print("7.0 / 2 = \(7.0 / 2)")
以上程序执行结果为:
7 / 2 = 3 (-7) / 2 = -3 7 % 3 = 1 (-7) % 3 = -1 7.0 / 2 = 3.5
如果一次运算的结果超出了类型能表示的范围,Swift 会直接让程序中止(触发陷阱 trap),而不是悄悄给出一个错误结果。
实例
let small: Int8 = 127
let overflow = small + 1
print(overflow)
程序会在执行到这一行时立刻中止,不会有任何输出,也不会把 overflow 赋成一个错误的值。
溢出运算符
Swift 提供 &+、&-、&* 三个溢出运算符,它们不会触发陷阱,而是让结果按位宽环绕。
环绕的结果是确定的:超出上限就从下限重新开始,反之亦然。
实例
print("Int.max = \(maxInt)")
print("Int.max &+ 1 = \(maxInt &+ 1)")
let minInt = Int.min
print("Int.min &- 1 = \(minInt &- 1)")
let x8: Int8 = 100
print("Int8 的 100 &* 2 = \(x8 &* 2)")
以上程序执行结果为:
Int.max = 9223372036854775807 Int.max &+ 1 = -9223372036854775808 Int.min &- 1 = 9223372036854775807 Int8 的 100 &* 2 = -56
注意:溢出运算符是给哈希、加密、游戏随机数这类需要明确环绕语义的场景准备的。普通业务计算仍然应该用 +、-、*,让溢出尽早暴露出来。
比较运算符
比较运算符返回一个 Bool,下表中的 A 为 10,B 为 20。
| 运算符 | 描述 | 示例 |
|---|---|---|
== | 等于 | (A == B) 为 false |
!= | 不等于 | (A != B) 为 true |
> | 大于 | (A > B) 为 false |
< | 小于 | (A < B) 为 true |
>= | 大于等于 | (A >= B) 为 false |
<= | 小于等于 | (A <= B) 为 true |
=== | 引用相等(两个变量指向同一个对象) | 仅用于类实例 |
!== | 引用不相等 | 仅用于类实例 |
实例
let B = 20
print("A == B 结果为:\(A == B)")
print("A != B 结果为:\(A != B)")
print("A > B 结果为:\(A > B)")
print("A < B 结果为:\(A < B)")
print("A >= B 结果为:\(A >= B)")
print("A <= B 结果为:\(A <= B)")
以上程序执行结果为:
A == B 结果为:false A != B 结果为:true A > B 结果为:false A < B 结果为:true A >= B 结果为:false A <= B 结果为:true
逻辑运算符
逻辑运算符操作 Bool 值,下表中的 A 为 true,B 为 false。
| 运算符 | 描述 | 示例 |
|---|---|---|
&& | 逻辑与,两侧都为 true 才为 true | (A && B) 为 false |
|| | 逻辑或,至少一侧为 true 就为 true | (A || B) 为 true |
! | 逻辑非,把 true 变 false,false 变 true | !A 为 false |
实例
let B = false
print("A && B 结果为:\(A && B)")
print("A || B 结果为:\(A || B)")
print("!A 结果为:\(!A)")
print("!B 结果为:\(!B)")
以上程序执行结果为:
A && B 结果为:false A || B 结果为:true !A 结果为:false !B 结果为:true
&& 和 || 是短路求值的:左侧已经能决定结果时,右侧不会再计算。
位运算符
位运算符直接对整数的二进制位进行操作,常用的有取反、与、或、异或和移位。
| 运算符 | 描述 | 示例(A = 60,B = 13) |
|---|---|---|
& | 按位与,同一位都为 1 才为 1 | A & B 结果为 12 |
| | 按位或,同一位有 1 就为 1 | A | B 结果为 61 |
^ | 按位异或,同一位不同才为 1 | A ^ B 结果为 49 |
~ | 按位取反,每一位都翻转 | ~A 结果为 -61 |
<< | 左移,低位补 0 | A << 2 结果为 240 |
>> | 右移,有符号数高位补符号位 | A >> 2 结果为 15 |
&<< | 溢出左移,移出的位直接丢弃,不检查 | 见下方示例 |
&>> | 溢出右移,移出的位直接丢弃,不检查 | 见下方示例 |
实例
let q = 13 // 二进制 0000 1101
print("p & q 结果为:\(p & q)")
print("p | q 结果为:\(p | q)")
print("p ^ q 结果为:\(p ^ q)")
print("~p 结果为:\(~p)")
print("p << 2 结果为:\(p << 2)")
print("p >> 2 结果为:\(p >> 2)")
以上程序执行结果为:
p & q 结果为:12 p | q 结果为:61 p ^ q 结果为:49 ~p 结果为:-61 p << 2 结果为:240 p >> 2 结果为:15
普通移位如果移出的位里含有有效数据,一样会触发溢出陷阱;&<< 和 &>> 则把移出的位直接丢掉。
实例
print("Int8 的 127 &<< 1 = \(small &<< 1)")
print("Int8 的 -128 &>> 1 = \((Int8.min) &>> 1)")
以上程序执行结果为:
Int8 的 127 &<< 1 = -2 Int8 的 -128 &>> 1 = -64
区间运算符
区间运算符用来表示一段连续的值,常见写法有四种。
| 写法 | 名称 | 含义 | 示例 |
|---|---|---|---|
a...b | 闭区间 | 包含 a 和 b | 1...5 为 1、2、3、4、5 |
a..<b | 半开区间 | 包含 a,不包含 b | 1..<5 为 1、2、3、4 |
...b | 单侧区间(前缀) | 从最小值到 b | array[...1] 取前两个元素 |
a... | 单侧区间(后缀) | 从 a 到最大值 | array[2...] 取第 3 个起的所有元素 |
实例
print("闭区间 1...3 -> \(i)")
}
for i in 1..<3 {
print("半开区间 1..<3 -> \(i)")
}
let names = ["Runoob", "RUNOOB", "菜鸟教程", "Swift"]
print(Array(names[...1])) // 前两个
print(Array(names[2...])) // 第 3 个起的所有元素
// 判断某个值是否落在区间内
print((1...5).contains(3))
print((1...5).contains(6))
以上程序执行结果为:
闭区间 1...3 -> 1 闭区间 1...3 -> 2 闭区间 1...3 -> 3 半开区间 1..<3 -> 1 半开区间 1..<3 -> 2 ["Runoob", "RUNOOB"] ["菜鸟教程", "Swift"] true false
空合运算符 ??
空合运算符 ?? 用来给可选值提供一个默认值:左侧有值就取左侧,为 nil 就取右侧。
它等价于 a != nil ? a! : b,但写法更短,而且只计算一次左侧表达式。
实例
let siteName2: String? = "www.runoob.com"
// 左侧为 nil,取默认值
print(siteName ?? "RUNOOB")
// 左侧有值,直接用左侧的值
print(siteName2 ?? "RUNOOB")
// 从字典取值也是可选类型,同样可以用 ??
let info: [String: String] = ["name": "Runoob"]
print(info["name"] ?? "未知")
print(info["site"] ?? "未知")
以上程序执行结果为:
RUNOOB www.runoob.com Runoob 未知
注意:?? 右侧的类型必须和左侧解包后的类型一致,或者能自动转换。更多可选类型的内容见第 29 篇《Swift 可选类型》。
三元运算符
三元运算符 条件 ? 值1 : 值2 是 if-else 的表达式写法,条件是 true 取「值1」,否则取「值2」。
实例
print(score >= 60 ? "及格" : "不及格")
let A = 1
let B = 2
let C = true
print(C ? A : B)
以上程序执行结果为:
及格 1
运算符优先级与结合性
一个表达式里出现多个运算符时,先算谁由优先级决定;同一优先级的运算符,则由结合性决定从左还是从右算起。
下表是 Swift 标准库中真实定义的优先级组,从高到低排列。
| 优先级组(从高到低) | 运算符 | 结合性 |
|---|---|---|
| BitwiseShiftPrecedence | << >> &<< &>> | 无 |
| MultiplicationPrecedence | * / % &* & | 左结合 |
| AdditionPrecedence | + - &+ &- | ^ | 左结合 |
| RangeFormationPrecedence | ..< ... | 无 |
| CastingPrecedence | is as | 无 |
| NilCoalescingPrecedence | ?? | 右结合 |
| ComparisonPrecedence | == != < <= > >= === !== | 无 |
| LogicalConjunctionPrecedence | && | 左结合 |
| LogicalDisjunctionPrecedence | || | 左结合 |
| TernaryPrecedence | ? : | 右结合 |
| AssignmentPrecedence | = += -= *= /= 等 | 右结合 |
注意:Swift 把 & 放在乘法优先级、把 | 和 ^ 放在加法优先级,这一点和 C 语言不同。在 C 里 a & b == c 会先算 ==,在 Swift 里则先算 &。写表达式时该加括号就加括号,别依赖记忆。
实例
let result = 2 + 3 * 4 % 5
print("2 + 3 * 4 % 5 = \(result)")
// 括号可以改变顺序
print("(2 + 3) * 4 % 5 = \((2 + 3) * 4 % 5)")
以上程序执行结果为:
2 + 3 * 4 % 5 = 4 (2 + 3) * 4 % 5 = 0
常见问题
下面几个问题在使用 Swift 运算符时最容易踩到。
| 问题 | 说明 |
|---|---|
为什么 7 / 2 得到 3 而不是 3.5 | 两个整数相除,结果是整数,小数被截断;要小数结果请用 7.0 / 2 |
| 溢出时为什么不报错而是崩溃 | Swift 默认对溢出做检查并触发陷阱,这是安全设计;确实需要环绕时用 &+ 系列 |
== 和 === 有什么区别 | == 比较值是否相等,=== 比较两个引用是否指向同一个对象 |
?? 左侧不是可选值能用吗 | 不能,它要求左侧是可选类型 |
| 区间运算符能直接当数组下标吗 | 可以,切片得到的是 ArraySlice,需要时用 Array() 转回数组 |
