Swift-基础运算符

运算符 是可以检查,修改或者组合值的特殊符号或者短语。例如,加法运算符(+)将两个数相加,像let i = 1 + 2,逻辑与运算符组合两个布尔值,像if enteredDoorCode && passedRetinaScan
Swift支持大多数标准C运算符并且改善了一些功能来避免常见的编码错误。赋值运算符(=)不会返回一个值,避免被错误的当成等于运算符(==)使用。数学运算符(+,-,*,/,%等等)会检测并且不允许值溢出,避免使用一些过大或者过小以致超出存储它们的类型允许范围的值而导致意外的结果。可以通过使用Swift的溢出运算符来处理值溢出的情况,详见溢出运算符
Swift同样提供了两个C没有的区间运算符(a..<ba...b),作为描述区间值的简写。
本章讲述Swift的基础运算符。高级运算符章节会介绍Swift的高级运算符,并会介绍如何定义一个自定义运算符,以及为你的自定义类型实现运算符重载。

目录

  • 术语
  • 赋值运算符
  • 数学运算符
  • 组合赋值运算符
  • 比较运算符
  • 三元条件运算符
  • Nil聚合运算符
  • 区间运算符
  • 逻辑运算符

术语

运算符可以是一元,二元者三元的:

  • 一元运算符作用于一个单独对象(例如-a)。一元前置运算符出现在对象的前面(例如!b),一元后置运算符出现在对象的后面(例如c!)。
  • 二元运算符作用于两个对象,是中置的,因为它出现在两个对象中间。
  • 三元运算符作用于三个对象。和C一样,Swift只有一个三元运算符:三元条件运算符(a ? b : c)。

运算符影响的值称为运算对象。在表达式1 + 2中,符号+是一个二元运算符,它的两个运算对象是值12

赋值运算符

赋值运算符(a = b)初始化或者更新a的值为b

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与OC的赋值运算符不同,Swift的赋值运算符本身不会返回一个值,以下声明是非法的:

if x = y {
    // This is not valid, because x = y does not 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与OC的数学运算符不同,Swift的数学运算符默认是不允许值溢出的。可以使用Swift的溢出运算符来处理值溢出的情况(例如a &+ b)。详见溢出运算符
加法运算符同样适用于String的拼接:

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

取余运算符

取余运算符 (a % b)会计算出b的多少倍最接近a,并返回剩余的值(也就是余数)。

注意
取余运算符(%)在其它语言中被称作模运算符 。但是在Swift中对于负数的这种操作,严格来说是一个取余运算而不是一个模运算。

下面展示了取余运算符是如何工作的。为了计算9 % 4,首先需要计算出4的多少倍最接近9

取余运算

你可以算出4的两倍最接近9,所以余数是1(图中橙色所示)。
在Swift中,可以写成:

9 % 4    // equals 1

为了确定a % b的答案,%运算符计算以下的等式并返回remainder作为输出:
a = (b x some multiplier) + remainder
在这个等式中,some multiplier是在满足最接近a的前提下b的最大乘数。
94带入等式,结果如下:
9 = (4 x 2) + 1
同样的方法也是用于当a是一个负数时:

-9 % 4   // equals -1

-94带入等式,结果如下:
-9 = (4 x -2) + -1
得出余数为-1
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标准库运算符参考

比较运算符

Swift提供所有标准C的比较运算符:

  • 等于(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 is not 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 is not 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信息,详见控制流
你同样也可以比较两个有同样数量值的元组,只要元组内的每个值都是可比较的。例如IntString是可以比较的,这意味着(Int, String)类型的元组是可比较的。与之相对,Bool是不能比较的,所以包含布尔类型的元组是不可比较的。
元组从左到右逐一比较,直到找到两个不同的值。这两个值的比较结果作为整个元组的比较结果。如果所有元素都相等,那么元组就相等。例如:

(1, "zebra") < (2, "apple")   // true because 1 is less than 2; "zebra" and "apple" are not 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",因为比较结果已经被元祖的第一个元素决定了。但是当元组的第一个元素相同时,第二个元素就会被比较 ,像2,3行那样。

注意
Swift标准库提供的元组比较运算符只支持元组元素个数小于7个的情况。当大等于7个时,需要自己实现比较运算符。

三元条件运算符

三元条件运算符 是一个由三部分组成的特殊运算符,形式为question ? answer1 : answer2。它是一个根据question是否为真来选取两个表达式其中之一的简写。如果为真,它选取answer1并返回它的值,如果为假,选取answer2并返回它的值。
三元条件运算符是以下形式代码的简写:

if question {
    answer1
} else {
    answer2
}

下面的例子用来计算table的行高。如果这个行有头部的话,行高要比contentHeight高出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可以用一行代码搞定,这要比第二个例子简洁的多。
三元条件运算符提供了处理二选一问题的高效方式。但是使用时也需要注意。如果过度使用,它的简洁性会导致代码难以阅读。所以应该避免将多个三元条件运算符的实例组合在一个组合声明中。

Nil聚合运算符

nil聚合运算符 (a ?? b)会解包可选型a,如果a包含一个值的话,否则返回默认值ba永远是一个可选类型。b必须符合a存储的数据类型。
nil聚合运算符是以下代码的简写:

a != nil ? a! : b

以上代码使用三元条件运算符,当a不是nil时,强制解包(a!)来获取a的解包值,否则返回b。nil聚合运算符以一种简洁和易读的格式提供了一种更加优雅的方式来包装这种条件检查和解包工作。

注意
如果a的值非nil,那么b的值不会被评估。这被称为短路估值

下面的例子使用nil聚合运算符在一种默认的颜色名和一个可选型的用户自定义颜色中选择一种:

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被定义为一个默认值为nil的可选型的String。因为userDefinedColorName是一个可选类型,你可以使用nil聚合运算符决定它的值。上面的例子中,这个运算符用来决定名为colorNameToUseString型变量的值。因为userDefinedColorNamenil,表达式userDefinedColorName ?? defaultColorName返回defaultColorName的值,依旧是“red”
如果userDefinedColorName被赋一个非nil值再次执行nil聚合运算符检查,userDefinedColorName解包的值会取代默认值被使用:

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is not nil, so colorNameToUse is set to "green"

区间运算符

Swift提供两种区间运算符,它们用来简化描述值的范围。

闭区间运算符

闭区间运算符 (a...b)定义了一个从ab,并且包括ab的范围。a的值必须小于b的值。
当遍历一个范围内的所有值时,闭区间运算符非常有用,例如一个for-in循环:

or 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的值,那么区间就是空的。
当处理以0为基数的列表例如数组时,半开区间运算符是极其有用的,这里它可以计数到但是不包括列表的长度:

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

注意观察数组包含4个元素,但是0..<count只计数到3(最后一个元素的索引),因为它是一个半开区间。更多关于数组信息,详见数组

逻辑运算符

逻辑运算符 修改或者组合布尔逻辑值truefalse。Swift支持基于C语言的三种标准逻辑运算符:

  • 逻辑非(!a)
  • 逻辑与(a && b)
  • 逻辑或(a || b)

逻辑非运算符

逻辑非运算符 (!a)转换一个布尔值,因此true变为falsefalse变为true
逻辑非运算符是一个前置运算符,它会立刻出现在运算对象的前面,没有任何空格。他可以被解读为“不是一个”,像下面例子所示:

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,右边的表达式将不会计算,因为右边的表达式已经不能改变整个表达式的结果。
在下面的例子中,第一个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!"

这个括号使得前两个值被看成整个逻辑中不同的可能状态的一部分。组合表达式的结果并没有改变,但是整个意图变得清晰易读。易读性总是优先于简洁性;使用括号来使你的意图明确。

上一篇:Swift-基础部分
下一篇:Swift-字符串和字符

推荐阅读更多精彩内容