Swift Learning (3) - Basic Operators (Fully Ver.)

Swift Learning (3) - Basic Operators (Fully Ver.)

August 10, 2021·Jensen
Jensen

image

运算符是检查、改变、合并值的特殊符号或短语。例如,加号+将两个数组相加(如let i = 1 + 2)。更复杂的运算例子包括逻辑与运算符&&(如if enteredDoorCode && passedRetinaScan)。

Swift所支持的运算符大家都可能在别的语言比如C语言中认识了,同时为了减少常见编码错误对它们做了部分改进。如:赋值符=不再有返回值,这样就消除了手误将判等运算符==写成赋值运算符导致代码出现错误的缺陷。

算术运算符(+、-、*、/、%等)的结果会被检测并禁止溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时到值的异常结果。当然允许使用Swift的溢出运算符来实现溢出。

Swift还提供了C语言没有的区间运算符,例如a..<ba...b,这方便我们表达一个区间内的数值。


术语

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

  • 一元运算符对单一操作对象操作(如 -a)。一元运算符分前置运算符和后置运算符,前置运算符需要紧跟在操作对象之前(如 !b),后置运算符需紧跟在操作对象之后(如 c!)
  • 二元运算符操作两个操作对象(2 + 3),是中置的,因为它们出现在两个操作对象之间
  • 三元运算符操作三个操作对象,和C语言一样,Swift只有一个三元运算符,就是三目运算符(a ? b : c)

受运算符影响的值叫操作数,在表达式1 + 2中,加号 + 是二元运算符,它的两个操作数是值1和2。


赋值运算符

赋值运算符(a = b),表示用b的值来初始化或更新a的值。

let b = 10
var a = 5
a = b  // a现在就等于b的值,10
print("将b的值赋给a,现在a的值为:\(a)")
---
output: b的值赋给a,现在a的值为:10

如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量。

var (x, y) = (2, 5)  // 现在x等于2,y等于5
print("元组(2, 5)被分解后,现在x等于:\(x),y等于:\(y)")
---
output: 元组(2, 5)被分解后,现在x等于:2y等于:5

C语言和Ojective-C语言不同,Swift的赋值操作并不返回任何值,所以下面的语句是无效的:

// if x = y {
     // 此结构错误,因为x = y并不返回任何值,报错Use of '=' in a boolean context, did you mean '=='?
// }

if x == y {
    // 此结构正常不报错
}

通过if x = y标记为无效语句,Swift能帮开发值避免把==错写成=这类错误的出现。


算术运算符

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

  • 加法(+)
  • 减法(-)
  • 乘法(*)
  • 除法(/)
print("1 + 2等于:", 1 + 2)  // 等于3
print("5 - 3等于:", 5 - 3)  // 等于2
print("2 * 3等于:", 2 * 3)  // 等于6
print("10.0 / 2.5等于:", 10.0 / 2.5)  // 等于4.0
---
output: 1 + 2等于: 3
5 - 3等于: 2
2 * 3等于: 6
10.0 / 2.5等于: 4.0

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

加法运算符也可用于String的拼接。

print("Hello " + "World!")  // 等于“Hello World!”
---
output: Hello World!

求余运算符

求余运算符(a % b)是计算b的多少倍刚好可以容入a,返回多出来的那部分(余数)。

注意

求余运算符(%)在其他语言中也叫取模运算符。但是严格来说,通过观察该运算符对负数的操作结果,“求余”比“取模”更合适些。

来看看取余操作究竟是怎么一回事,计算9 % 4,先计算4的多少倍会刚好可以容入9中:

remainder_operator

可以在9中放入两个4,那余数是1

Swift中可以表达为。

print("9 % 4的结果是:\(9 % 4)")  // 等于1
---
output: 9 % 4的结果是:1

同样的方法来计算 -9 % 4

print("-9 % 4的结果是:\(-9 % 4)")  // 等于-1
---
output: -9 % 4的结果是:-1

在对负数b取余时,b的符号会被忽略。这意味着a % ba % -b的结果是相同的。

print("9 % -4的结果是:\(9 % -4),与9 % 4的结果相同。")
---
output: 9 % -4的结果是:1,与9 % 4的结果相同。

一元负号运算符

数值的正负号可以使用前缀-(即一元负号运算符)来切换。

let three = 3
let minusThree = -three  // minusThree等于-3
let plusThree = -minusThree  // plusThree等于3,即“负负得正”
print("负负得正的结果是:\(plusThree)")
---
output: 负负得正的结果是:3

一元正号运算符

一元正号运算符+不做任何改变地返回操作数的值。

let minusSix = -6
let alsoMinusSix = +minusSix
print("正号运算符啥都不会改变,所以alsoMinusSix的值为:\(alsoMinusSix)")
---
output: 正号运算符啥都不会改变,所以alsoMinusSix的值为:-6

虽然一元正好运算符啥都不会改变,但是当使用一元负号运算符来表达负数时,可以同时使用一元正号运算符来表达整数,这样会保持代码的对称美。


组合赋值运算符

如同C语言,Swift也提供把其他运算符和赋值运算符=组合的组合赋值运算符,组合加运算+=是其中一个例子。

var c = 2
c += 2
print("c经过组合加运算后的值是:\(c)")  // c现在是3
---
output: c经过组合加运算后的值是:4

表达式a += 2a = a + 2的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。

注意

复合赋值运算没有返回值。let b = a += 2这类代码是错误的,这不同于上面提到的自增和自减运算符。


比较运算符(Comparison Operators)

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

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

注意

Swift也提供恒等(===)和不恒等(!==)这两个比较符来判断两个对象是否引用同一个对象实例。

每个比较运算符都返回了一个标识表达式。

print("1等于1:\(1 == 1)")  // true
print("2不等于1:\(2 != 1)")  // true
print("2大于1:\(2 > 1)")  // true
print("1小于2:\(1 < 2)")  // true
print("1大于等于1:\(1 >= 1)")  // true
print("2小于等于1:\(2 <= 1)")  // false
---
output: 1等于1true
2不等于1true
2大于1true
1小于2true
1大于等于1true
2小于等于1false

比较运算多用于条件语句,如if条件。

let name = "Jensen"
if name == "world" {
    print("Hello, World!")
} else {
    print("I'm sorry \(name), but I don't recognize you.")
}
---
output: I'm sorry Jensen, but I don't recognize you.

如果两个元组的元素类型相同且长度相同,元组就可以比较。比较元组大小会按照从左到右,逐值比对的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组就被称为相等的。

print("元组比较1:\((1, "zebra") < (2, "apple"))")  // true  因为1小于2,所以(1, "zebra") < (2, "apple"),虽然"zebra" > "apple",但对最后的结果没有影响
print("元组比较2:\((3, "apple") < (3, "bird"))")  // true  当第一个元素相同时,元组的第二个元素会进行比较
print("元组比较3:\((4, "dog") == (4, "dog"))")  // true
---
output: 元组比较1true
元组比较2true
元组比较3true

因此,当元组中的元素都可以被比较时,可以使用这些运算符来比较它们的大小。例如,可以比较两个类型为(String, Int)的元组,因为Int和String类型的值都是可以比较的。相反Bool不能被比较,也意味着存有布尔类型的元组不能被比较。

print("String和Int类型元素是可以被比较的:\(("blue", -1) < ("purple", 1))")  // 正常,比较的结果为true
//("blue", false) < ("purple", true)  // 错误,因为 < 不能比较布尔类型值,若取消注释,则报错Binary operator '<' cannot be applied to two '(String, Bool)' operands
---
output: StringInt类型元素是可以被比较的:true

注意

Swift标准库只能比较七个以内元素的元组的比较,如果元组中的元素个数超过六个,则需要自己实现比较运算符。


三元运算符

三元运算符的特殊主要在于它是有三个操作数的运算符,它的形式是问题 ? 答案1 : 答案2。它简洁地表述问题成立与否作出二选一的操作。如果问题成立,返回答案1的结果,反之返回答案2的结果。

三元运算符试一下代码的缩写形式。

let question = 3 > 2
let answer1 = 3, answer2 = 2
print("三元运算符的结果:\(question ? answer1 : answer2)")  // 三元运算符

if question {
    print("answer1的结果是:\(answer1)")
} else {
    print("answer2的结果是:\(answer2)")
}
---
output: 三元运算符的结果:3
answer1的结果是:3

这里有个计算表格里行高的例子,如果有表头,那行高应该比内容高度要高出50点;如果没有表头,只需要高出20点。

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)  // rowHeight现在是90
print("rowHeight现在的值是:\(rowHeight)")
---
output: rowHeight现在的值是:90

上面的写法比下面的代码更加简洁。

let contentHeights = 40
let hasHeaders = true
var rowHeights = contentHeights
if hasHeaders {
    rowHeights += 50
    print("hasHeaders = True, rowHeights: \(rowHeights)")
} else {
    rowHeights += 20
    print("hasHeaders = False, rowHeights: \(rowHeights)")
}
---
output: hasHeaders = True, rowHeights: 90

第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将rowHeight定义成变量,因为它的值无需在if语句中改变。

三元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,为了提高代码的可读性,应当避免在一个复合语句中使用多个三元运算符。


空合运算符(Nil Coalescing Operator)

空合运算符a ?? b将对可选类型a进行空判断,如果a包含一个值就进行解包,否则就返回一个默认值b。表达式a必须是Optional类型。默认值b的类型必须要和a存储值的类型保持一致。

空合运算符是对一下代码的简短表达方法。

var d: Int? = 3
let e: Int? = nil
print("空合运算符的原始表达的结果:\((d != nil ? d! : e)!)")  // d != nil ? d! : e
---
output: 空合运算符的原始表达的结果:3

上述代码使用了三元运算符。当可选类型a的值不为空时,进行强制解包a!,访问a中的值;反之返回默认值b。无疑空合运算符??提供了一种更为优雅的方式去封装条件判断合解包两种行为,显得简洁且以及更具有可读性。

注意

如果a为非空值(Non-nill),那么值b将不会被计算,这就是所谓的短路求值。

下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间的抉择。

let defaultColorName = "red"
var userDefinedColorName: String?
var colorNameToUse = userDefinedColorName ?? defaultColorName  // userDefinedColorName的值为Nil,所以colorNameToUse的值为“red”
print("colorNameToUse的颜色是:\(colorNameToUse)")
---
output: colorNameToUse的颜色是:red

由于userDefinedColorName值为空,因此表达式userDefinedColorName ?? defaultColorName返回defaultColorName的值,即red

如果分配一个非空值(non-nil)给userDefinedColorName,再次执行空合运算,运算结果为封包在userDefinedColorName中的值。而非默认值。

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
print("colorNameToUse的颜色是:\(colorNameToUse)")  // userDefinedColorName非空,因此colorNameToUse的值为“green”
---
output: colorNameToUse的颜色是:green

区间运算符(Range Operators)

Swift提供了集中方便表达一个区间的值的区间运算符。

闭区间运算符

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

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

for index in 1...5 {
    print("\(index) * 5 = \(index * 5)")
}
---
output: 1 * 5 = 5
2 * 5 = 10
3 * 5 = 15
4 * 5 = 20
5 * 5 = 25

半开区间运算符

半开区间运算符a..<b定义一个从ab但不包括b的区间。之所以被称为半开区间,是因为该区间包含第一个值而不包括最后的值。

半开区间的实用性在于当使用一个从0开始的列表(如数组)时,能够非常方便地从0数到列表的长度。

let names = ["Jensen", "Alex", "Davis", "Jerry"]
let count = names.count
for i in 0..<count {
    print("第\(i + 1)个人叫\(names[i])")
}
---
output: 1个人叫Jensen
2个人叫Alex
3个人叫Davis
4个人叫Jerry

数组有4个元素,但0..<count只数到3(最后一个元素的下标),因为其是半开区间。

单侧区间

闭区间操作符有另一个表达形式,可以表达往一侧无限延伸的区间。例如,一个包含了数组从索引2到结尾的所有值的区间。在这些情况下,可以省略掉区间操作符一侧的值,这种区间叫单侧区间。

for name in names[2...] {
    print("names[2...], ", name)
}

for name in names[...2] {
    print("names[...2], \(name)")
}
---
output: names[2...],  Davis
names[2...],  Jerry
names[...2], Jensen
names[...2], Alex
names[...2], Davis

半开区间操作符也有单侧表达式,附带上它的最终值。就像使用区间去包含一个值,最终值并不会落在区间内。

for name in names[..<2] {
    print("names[..<2], \(name)")
}
---
output: names[..<2], Jensen
names[..<2], Alex

单侧区间不止可以在下标里使用,也可以在别的情境下使用。也可以使用查看单侧区间时都包含某个特定的值。

let range = ...5
print("range.contains(7): \(range.contains(7))")  // false
print("range.contains(4): \(range.contains(4))")  // true
print("range.contains(-1): \(range.contains(-1))")  // true
---
output: range.contains(7): false
range.contains(4): true
range.contains(-1): true

注意

不能遍历省略了初始值的单侧区间,因为遍历的开端并不明显。但可以遍历一个省略最终值的单侧区间。然而由于这种区间无限延伸的特性,请保证在循环里有一个结束循环的分支。


逻辑运算符(Logical Operators)

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

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

逻辑非运算符

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

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

let allowedEntry = false
if !allowedEntry {  // 当allowedEntry为false,即!allowedEntry为true时,运行代码块
    print("ACCESS DENIED")  // 输出“ACCESS DENIED”
}
---
output: ACCESS DENIED

开发过程中小心地选择布尔常量或变量有助于代码的可读性,并且避免使用双重逻辑非运算或混乱的逻辑语句。

逻辑与运算符

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

只要任意一个表达式是false,整个表达式的值就为false。事实上如果第一个表达式的值为false,那么是不去计算第二个值的,因为后面的值已经不可能影响整个表达式的结果了,这被称作段路计算(Short-Circuit Evaluation)。

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {  // passedDoorCode的值是false,所以整个表达式的值也是false
    print("Welcome!")
} else {
    print("ACCESS DENIED")  // 输出"ACCESS DENIED"
}
---
output: ACCESS DENIED

逻辑或运算符

逻辑或运算符a || b是一个由两个连续的|组成的中置运算符。它表示了两个逻辑表达式的其中一个为true,整个表达式就为true

同逻辑运算符类似,逻辑或也是“短路计算”的,当左端的表达式为true时,将不计算右边的表达式了,因为其也不可能改变整个表达式的值了。

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {  // knowsOverridePassword的值是true,所以整个表达式的值是true
    print("Welcome!")  // 输出“Welcome!”
} else {
    print("ACCESS DENIED")
}
---
output: Welcome!

逻辑运算符组合计算

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

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

上述例子使用了含多个&&||的复合逻辑。无论怎样,&&||始终只能操作两个值,所以这实际上是三个简单逻辑连续操作的结果。

注意

Swift逻辑操作符&&和||是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。

使用括号来明确优先级

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

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

这括号使得前两个值被看成整个逻辑表达中独立的一个部分,虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰,可读性比和简洁性一样非常重要。

Last updated on