现在位置: 首页 > Swift 教程 > 正文

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 A = 10
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) / 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),而不是悄悄给出一个错误结果。

实例

// Int8 的范围是 -128...127,下面这行会在运行时触发溢出陷阱
let small: Int8 = 127
let overflow = small + 1
print(overflow)

程序会在执行到这一行时立刻中止,不会有任何输出,也不会把 overflow 赋成一个错误的值。


溢出运算符

Swift 提供 &+、&-、&* 三个溢出运算符,它们不会触发陷阱,而是让结果按位宽环绕。

环绕的结果是确定的:超出上限就从下限重新开始,反之亦然。

实例

let maxInt = Int.max
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 A = 10
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 A = true
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 才为 1A & B 结果为 12
|按位或,同一位有 1 就为 1A | B 结果为 61
^按位异或,同一位不同才为 1A ^ B 结果为 49
~按位取反,每一位都翻转~A 结果为 -61
<<左移,低位补 0A << 2 结果为 240
>>右移,有符号数高位补符号位A >> 2 结果为 15
&<<溢出左移,移出的位直接丢弃,不检查见下方示例
&>>溢出右移,移出的位直接丢弃,不检查见下方示例

实例

let p = 60   // 二进制 0011 1100
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

普通移位如果移出的位里含有有效数据,一样会触发溢出陷阱;&<< 和 &>> 则把移出的位直接丢掉。

实例

let small: Int8 = 127
print("Int8 的 127 &<< 1 = \(small &<< 1)")

print("Int8 的 -128 &>> 1 = \((Int8.min) &>> 1)")

以上程序执行结果为:

Int8 的 127 &<< 1 = -2
Int8 的 -128 &>> 1 = -64

区间运算符

区间运算符用来表示一段连续的值,常见写法有四种。

写法名称含义示例
a...b闭区间包含 a 和 b1...5 为 1、2、3、4、5
a..<b半开区间包含 a,不包含 b1..<5 为 1、2、3、4
...b单侧区间(前缀)从最小值到 barray[...1] 取前两个元素
a...单侧区间(后缀)从 a 到最大值array[2...] 取第 3 个起的所有元素

实例

for i in 1...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 siteName: String? = nil
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」。

实例

let score = 85
print(score >= 60 ? "及格" : "不及格")

let A = 1
let B = 2
let C = true
print(C ? A : B)

以上程序执行结果为:

及格
1

运算符优先级与结合性

一个表达式里出现多个运算符时,先算谁由优先级决定;同一优先级的运算符,则由结合性决定从左还是从右算起。

下表是 Swift 标准库中真实定义的优先级组,从高到低排列。

优先级组(从高到低)运算符结合性
BitwiseShiftPrecedence<< >> &<< &>>无
MultiplicationPrecedence* / % &* &左结合
AdditionPrecedence+ - &+ &- | ^左结合
RangeFormationPrecedence..< ...无
CastingPrecedenceis as无
NilCoalescingPrecedence??右结合
ComparisonPrecedence== != < <= > >= === !==无
LogicalConjunctionPrecedence&&左结合
LogicalDisjunctionPrecedence||左结合
TernaryPrecedence? :右结合
AssignmentPrecedence= += -= *= /= 等右结合

注意:Swift 把 & 放在乘法优先级、把 | 和 ^ 放在加法优先级,这一点和 C 语言不同。在 C 里 a & b == c 会先算 ==,在 Swift 里则先算 &。写表达式时该加括号就加括号,别依赖记忆。

实例

// 先算 3 * 4,再算 % 5,最后加 2
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() 转回数组