iOS
Swift

Swift 5.1 02 基本运算符

简介:Swift 基本运算符,翻译自swift.org

简介

运算符是用于检查、更改或组合值的特殊符号或短语。例如,加法运算符(+)添加两个数字,如let i=1+2,逻辑与运算符(&&)组合两个布尔值,如if enteredDoorCode && passedRetinaScan.

Swift支持大多数标准的C运算符,并改进了几种消除常见编码错误的功能。赋值运算符(=)没有返回值,以防止在预期使用等于运算符(=)时错误地使用它。算术运算符(+-*/%等)检测并禁止值溢出,以避免在处理大于或小于存储它们的类型的允许值范围的数字时出现意外结果。我们可以选择使用swift的溢出操作符来值溢出行为,如溢出操作符中所述。

Swift还提供在c语言中没有的范围运算符,例如a.<ba…b,作为表示值范围的快捷方式。

本章介绍了swift中的常用运算符。高级操作符涵盖了Swift的高级操作符,并描述了如何定义自己的自定义操作员和如何进行自定义类型的运算符重载。

术语 (Terminology )

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

一元运算符对单个目标(如-a)进行操作。一元前缀运算符出现在其目标(例如!b)和一元后缀操作符出现在它们的目标之后(例如c!).
二元运算符对两个目标(如2 + 3)进行操作,由于它们出现在两个目标之间,所以它们是中缀。
三元运算符作用于三个目标。像c语言一样,swift只有一个三元运算符,即三元条件运算符(a ? b : c)。
运算符影响的值是操作数。在表达式1 + 2中,+符号是一个二元运算符,它的两个操作数是值12

赋值运算符 (Assignment Operator)

赋值运算符(assignment operator)基于右值(right operand)的值,给左值(left operand)赋值。
赋值运算符(a = b)用b值初始化或更新a的值:

    let b = 10
    var a = 5
    a = b
    print("a = \(a), b = \(b)")
    // 输出结果 a = 10, b = 10

如果赋值预算符的右边是一个具有多个值的元组,那么它的元素可以一次分解为多个常量或变量,比如以下:

    let (x, y) = (1, 2)
    print("x = \(x), y = \(y)")
    // 输出结果 x = 1, y = 2

与c语言和Objective-C语言中的赋值运算符不同,Swift中的赋值运算符本身没有返回值。所以下代码无效且无法编译用过:

    if x = y {
        // `if x = y`是无效的,因为`x = y`没有返回值,所以无法编译通过。
    }

if x = y是无效的,实际上我们应该使用等于运算符==,而却赋值运算符=

算术运算符 (Arithmetic Operators)

Swift支持所有数值类型的四个标准算术运算符:
- Addition加法(+
- Subtraction减法(-
- Multiplication乘法(*
- Division除法(/

    let add = 1 + 2  // 结果为 3
    let sub = 10 - 9 // 结果为 1
    let mult = 7 * 2 // 结果为 14
    let div = 120.8 / 20 // 结果为 6.04
    print("add = \(add)")
    print("sub = \(sub)")
    print("mult = \(mult)")
    print("div = \(div)")

与c和Objective-C中的算术运算符不同的是,Swift算术运算符默认不允许值溢出。那么我们可以选择使用swift的溢出运算符(如a &+ b)来值溢出行为。请参见溢出运算符

通过加法运算符对字符串的拼接,比如:

    print("hello, " + "world")
    // 输出结果为 "hello, world"

1.余数运算符 (Remainder Operator)

余数运算符,也叫取模运算符,(a % b)计算出b在a中的倍数,并返回剩余的值(称为余数)。

  • 语法

    var1 % var2
    

  • 示例

    12 % 5 // 2
    -1 % 2 // -1
    1 % 2 // 1
    2 % 3 // 2
    -4 % 2 // -0
    5.5 % 2 // 1.5
    

注意
余数运算符(%)在其他语言中也称为取模运算符。然而,它对于负数的swift行为意味着,严格来说,它是一个余数而不是一个取模运算

下面是余数运算符的工作原理。要计算9 % 4,首先要计算出 4 的多少倍会刚好可以容入 9 中:
remainderInteger_2x.png
通过上图,我们可以看到9内放了两个4,那么余数就是1(以橙色显示)。

在swift中取余数运算,可以这么写:

9 % 4  // 结果,也就是余数为 1

要就是那a % b的结果,%运算符将计算出余数的结果并作为返回值返回:
a = (b x 倍数) + 余数

当倍数取最大值的时候,就会刚好可以放入到 a 中。
通过以上的说明,我们可以到达9 % 4的余数结果,那么他们的公式为:
9 = (4 x 2) + 1

在计算一个负数数(-9)的余数时,可使用相同的方法计算出余数:

-9 % 4 // 余数结果为-1

在方程式中插入-9和4可得出:

-9 = (4 x -2) + -1

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

    print("10 % 3 = \(10 % 3), 10 % -3 = \(10 % -3)")
    // 输出结果 10 % 3 = 1, 10 % -3 = 1

2.一元负号运算符 (Unary Minus Operator)

数值的符号可以使用前缀-即一元负号运算符进行切换:

    let three = 3
    let minusThree = -three       // minusThree 的值为 -3
    let plusThree = -minusThree   // plusThree 的值为 3
    print("three=\(three), minusThree=\(minusThree), plusThree=\(plusThree)")
    // 输出结果 three=3, minusThree=-3, plusThree=3

一元负号运算符(-)直接写在它所操作的值之前(比如 -3),之间不能有任何空格(- 3 是不可以的)。

3.一元正号操作符 (Unary Plus Operator)

一元正号运算符(+)只返回其操作的值,而不做任何更改:

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

虽然一元正号运算符实际上不做任何操作,但在代码中使用它为正数提供对称性,同时也使用一元减号运算符为负数提供对称性。

复合赋值运算符 (Compound Assignment Operators)

与c语言相似,swift提供了将赋值(=)与另一个操作组合在一起的复合赋值运算符。下面的例子是加法赋值运算符(+=):

var a = 1
a += 2
// a 现在等于 3

表达式a += 2a = a + 2的简写。实际上,加法和赋值被组合成一个同时执行两个任务的运算符。

注意
复合赋值运算符没有返回值。例如,你不能写b = a += 2

有关swift标准库提供的操作符的信息,请参阅操作符声明

比较运算符 (Comparison Operators)

Swift支持c语言中所有的标准比较运算符:
- 等于 (a == b)
- 不等于 (a != b)
- 大于 (a > b)
- 小于 (a < b)
- 大于等于 (a >= b)
- 小于等于 (a <= b)

注意
Swift还提供两个标识运算符(==!==),用于测试两个对象引用是否都引用同一对象实例。有关详细信息,请参见标识运算符

  • 语法

    var1 == var2
    

  • 返回值

    Bool 类型
    

  • 示例

    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
    

每个比较运算符都有一个Bool类型的返回值,以表示该语句的结果是否为true

  • 了解比较运算符
    比较运算符通常用于条件判断语句,例如if语句:
        let name = "world"
        if name == "world" {
            print("hello world.")
        }
        else {
            print("I'm sorry \(name), but I don't recognize you")
        }
        // 输出结果 hello world.
    

有关if语句的更多信息,请参见控制流

  • 用比较运算符比较两个元组
    如果两个元组具有可比较性(比如两个元组中有相同的类型和值的数量),那么我们也可以比较运算符比较它们。元组遵循从左到右进行比较,每次比较一个值,直到比较找到两个不相等的值。对这两个值进行比较,比较的结果决定了元组比较的总体结果。如果所有元素都相等,那么元组本身就相等。例如:

        let flag = (1, "zebra") < (2, "apple") // true 这两个元组的第一个元素 1 小于 2,第二个元素"zebra" 和 "apple" 没有比较,所以以第一个元素的比较结果为这两个元组的比较结果
        let flag1 = (3, "apple") < (3, "bird") // true 因为这两个元组的第一个元素 3 相等,并且“apple" 小于 "bird"(比较字母),所以按照第二个元素的比较结果为这两个元组的比较结果
        let flag2 = (4, "dog") == (4, "dog") // true 这两个元素的两个元素都是相同的,所以这两个元组相同
        print("flag=\(flag), flag1=\(flag1), flag2\(flag2)")
        // 输出结果 flag=true, flag1=true, flag2true
    

    在上面的示例中,我们可以在第一行看到比较的规则时从左到右的顺序。因为1小于2,所以不管元组中是否有其他值,(1, "zebra")被认为小于(2, "apple")。虽然"zebra"并不比"apple"小,但是比较已经由元组(tuples)的第一个元素决定了。只有当元组的第一个元素相同时,会比较它们的第二个元素,这就是在第二行和第三行发生的情况。

  • 对具有不可比较性元素的元组使用比较运算符比较时,无法编译通过
    只有当运算符可以应用于各自元组中的每个值时,才能将元组与给定的运算符进行比较。例如,如下面的代码所示,可以比较两个类型(String, Int)的元组,因为可以使用<运算符比较StringInt的值。相反,两个类型(String, Bool)的元组不能与<运算符进行比较,因为<运算符不能应用于Bool值。

       let flag3 = ("blue", -1) < ("purple", 1) // 可以比较
        ("blue", false) < ("purple", true)  // 不可以比较,因为`<`小于比较运算符不适用于布尔值之间的比较,所有无法编译通过,编译错误:`Binary operator '<' cannot be applied to two '(String, Bool)' operands`
    

注意:
Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。

三目运算符 (Ternary Conditional Operator)

三目条件算符,也叫三元条件运算符,它是一个特殊操作符,由三个操作部分组成,采用问答语句的形式。问题 ? 答案1 : 答案2。它是根据问题的真假来确定结果是哪个答案。如果问题为真,则返回答案1的值;否则,返回答案2的值。

三元条件运算符是下面代码的简写:

if question {
    answer1
} else {
    answer2
}

下面是使用三目运算符计算表格行高度的示例。如果这行有标题,此行高应比内容高度高50点;如果行没有标题,则行高20点:

    let contentHeight = 40
    let hasHeader = true
    let rowHeight = contentHeight + (hasHeader ? 50 : 20)
    print("rowHeight=\(rowHeight)")
    // 输出结果 rowHeight=90

下面我们不使用三目运算符完成以上示例的计算:

    let contentHeight = 40
    let hasHeader = true
    let rowHeight: Int
    if hasHeader {
        rowHeight = contentHeight + 50
    }
    else {
        rowHeight = contentHeight + 20
    }
    print("rowHeight=\(rowHeight)")

第一个示例使用三目运算符,只需要一行代码完成计算,比第二个示例简洁的多。

三目运算符提供了一个有效的速记来决定要考虑的两个表达式中的哪一个,虽然它足够的简洁,但是这样会导致代码难以阅读。所以我们要尽量的避免在成一个组合语句中使用多个三目运算符。

nil合并运算符 (Nil-Coalescing Operator)

nil合并运算符(a ?? b),如果可选类型a包含值,则对其进行解包,并返回解包后a的值;如果anil,则返回默认值b。表达式a始终是可选类型。表达式b必须与存储在a中的类型匹配。

使用三目运算符实现nil 的聚合运算符:

    let a: Int? = 10
    let b = 20
    let c = a != nil ? a! : b
    print("c = \(c)")
    // 输出结果 c = 10

上面的代码使用三元条件运算符和强制解包(a!),当a不为nil时,访问a中包装的值,否则返回bnil合并运算符提供了一种更优雅的方法,以简洁易读的形式封装条件检查和解包。

注意
如果a的值不为nil,则不计算b的值。这就是所谓的短路评估。

  • 使用nil合并运算符需要满足两个条件:
    a 必须是Optional(可选类型)的。
    b 的类型必须要和a解包后的值类型一致。

下面的示例使用nil合并运算符,在默认颜色名称和用户定义的颜色名称(可选类型)之间进行选择:

    let defaultColorName = "red"
    var userDefinedColorName: String?   // 默认为nil
    var colorNameToUse = userDefinedColorName ?? defaultColorName
    print("colorNameToUse = \(colorNameToUse)")
    // 输出结果 colorNameToUse = red,因为`userDefinedColorName` 为nil, 因此`colorNameToUse`被设置为默认的red

userDefinedColorName变量定义为一个可选类型的字符串,默认值为nil。由于userDefinedColorName是可选类型,因此可以使用nil合并运算符来计算其值。在上面的示例中,运算符用于确定名为colorNameToUse的字符串变量的初始值。因为userDefinedColorNamenil,所以表达式userDefinedColorName ?? defaultColorName返回defaultColorName变量的值”red”。

当我们给userDefinedColorName赋值一个非nil的值以后在对其进行nil合并运算,那么结果将返回userDefinedColorName解包后的值,而不再是默认值”red”。

    userDefinedColorName = "green"
    colorNameToUse = userDefinedColorName ?? defaultColorName
    print("colorNameToUse = \(colorNameToUse)")
    // 输出结果 colorNameToUse = green,因为`userDefinedColorName` 已经赋值为非nil的值,所以s不再返回默认值

范围运算符 (Range Operators)

swift包含几个范围运算符,它们是表示获取某个范围的值的快捷方式。

闭合范围运算符
  • 语法
    a...b
    

    闭合范围运算符定义从ab的范围,并包括值aba的值不得大于b
    当我们在希望在某个值的某些范围内迭代时(比如 for in),闭合范围运算符很有用:
        for index in 0...5 {
            print("\(index) times 5 is \(index * 5)")
        }
        /*输出
         0 times 5 is 0
         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的更多信息,请参见控制流
半开范围运算符 (Half-Open Range Operator)
  • 语法
    a..<b
    

    半开范围运算符定义了从ab的范围,但不包括b。因为它包含其第一个值,但不包含其最终值,所以说它是半开的。与闭合范围运算符一样,a的值不能大于b。如果a的值等于b,则结果范围将为空。

半开范围运算符在处理基于索引为0列表或者数组时非常有用,我们在遍历数组时使用半开范围运算符,可以非常方便的从第0个访问到数组的最后位置,下面使用半开范围运算符,访问数组中的每个元素:

    let names = ["Anna", "Alex", "Brian", "Jack"]
    let count = names.count
    for index in 0..<count {
        print("这个人的名字是: \(names[index])")
    }
    /*
     输出结果:
     这个人的名字是: Anna
     这个人的名字是: Alex
     这个人的名字是: Brian
     这个人的名字是: Jack
     */

请注意,上面示例中,names这个数组中有4个元素,因为它是半开放范围,所有0..<count只会取值到第3个(也就是数组中最后一个元素的索引)。有关数组的更多信息,请参见数组

单侧范围运算符 (One-Sided Ranges)
  • 语法
    2...
    

单侧范围运算符是闭合范围运算符的另一种替代形式,从swift4引入的新特性,适用于从一个方向的某个索引开始的范围,那就是单侧范围运算符。例如,包含从索引2到数组末尾的数组所有元素的范围。在这些情况下,可以省略范围运算符一侧的值。这种范围称为单侧范围,因为运算符只有一侧有一个值。例如:

    let names = ["Anna", "Alex", "Brian", "Jack"]
    // `2...`,从第二个元素开始遍历,一直到最后一个元素
    for name in names[2...] {
        print("name=\(name)")
    }
    /*
     输出结果
     name=Brian
     name=Jack
     */

    // `...2`,取第0~2之间的范围
    for name in names[...2] {
        print("name=\(name)")
    }
    /*
     输出结果
     name=Alex
     name=Brian
     */

半开范围运算符也有一个只使用其最终值编写的单侧窗体。就像在两边都包含一个值一样,最终值不在范围内。例如:

    // 半开范围运算符也有类似于单侧范围运算符的使用方式。它是从首位开始一直到限定范围前的索引,但是其并不包含限定的索引。例如:
    for name in names[..<2] {
        print("name=\(name)")
    }
    /*
     输出结果
     name=Anna
     name=Alex
     */

单侧范围运算符不单单可以在数组的下标中使用。我们不能迭代忽略第一个值的单侧范围,因为不清楚迭代应该从哪里开始。 我们可以迭代忽略其最终值的单侧范围; 但是,因为范围无限期地继续,请确保为循环添加显式结束条件。 您还可以检查单侧范围是否包含特定值,如下面的代码所示:

    let range = ...5
    print(range.contains(7)) // false 7不在`...5` 的范围中
    print(range.contains(5)) // true 5 在`...5` 的范围中
    print(range.contains(4)) // true 4 在`...5` 的范围中
    print(range.contains(-1)) // true -1 在`...5` 的范围中

逻辑运算符 (Logical Operators)

逻辑运算符运算的结果返回值为Bool类型布尔逻辑值truefalse。 Swift支持基于c语言三个标准的逻辑运算符:
逻辑非(Logical NOT ) (!a)
逻辑与(Logical AND) (a && b)
逻辑或(Logical OR) (a || b)

逻辑非运算符 (Logical NOT Operator)
  • 语法
    !a
    

    逻辑非运算符(!a)对布尔值进行反转操作,使true变为false,而false变为true

逻辑非运算符是前缀运算符,并立即出现在其操作的值之前,没有任何空白。它可以理解为!a,如下面的示例所示:

    let allowedEntry = true
    if !allowedEntry == false {
        print("ACCESS DENIED")
    }
    // 输出结果 ACCESS DENIED

!allowedEntry可以理解为“如果不允许进入”。只有当“不允许进入”为true时,才执行下面的代码;也就是说,如果allowedEntryfalse,则执行后面的代码。

在本例中,仔细选择布尔常量和变量名有助于保持代码可读性和简洁性,同时避免双重否定或混淆逻辑语句。

逻辑与运算符 (Logical AND Operator)
  • 语法
    a && b
    

    逻辑与运算符(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")
    }
        let enteredDoorCode = true
    let passedRetinaScan = false
    if enteredDoorCode && passedRetinaScan {
        print("Welcome!")
    } else {
        print("ACCESS DENIED")
    }
    // 输出结果  ACCESS DENIED

逻辑或运算符 (Logical OR Operator)
  • 语法
    a || b
    

    逻辑或运算符(a || b)是由两个相邻操作数组成的中缀运算符。我们可以使用它来创建逻辑表达式,其中只有两个值中的一个必须为true才能使整个表达式为true

和上面的逻辑与运算符一样,逻辑或运算符也使用短路计算来考虑其表达式。如果逻辑或表达式的左侧为true,则不会计算右侧,因为它无法更改整个表达式的结果。

在下面的示例中,第一个Bool类型的常量hasDoorKey的值为false,而第二个常量knowsOverridePassword的值为true。因为这两个其中一个值为true,所以整体表达式的计算结果也为true,并且允许继续执行if语句中的代码:

// 逻辑或运算符 (Logical OR Operator)
func logicalOrOperator() -> Void {
    let hasDoorKey = false
    let knowsOverridePassword = true
    if hasDoorKey || knowsOverridePassword {
        print("Welcome!")
    }
    else {
        print("ACCESS DENIED")
    }
    // 输出结果 Welcome!
}

组合逻辑运算符 (Combining Logical Operators)

可以组合多个逻辑运算符以创建较长的复合表达式:

   let enteredDoorCode = true
    let passedRetinaScan = false
    let hasDoorKey = false
    let knowsOverridePassword = true
    if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
        print("Welcome!")
    } else {
        print("ACCESS DENIED")
    }
    // 输出结果 Welcome!

这个例子使用了含多个 &&||的复合逻辑。但无论怎样,&&||始终只能操作两个值。所以这实际是三个简单逻辑连续操作的结果。我们来解读一下:

如果我们输入了正确的密码并通过了视网膜扫描,或者我们有一把有效的钥匙,又或者我们知道紧急情况下重置的密码,我们就能把门打开进入。

前两种情况,我们都不满足,所以前两个简单逻辑的结果是false,但是我们是知道紧急情况下重置的密码的,所以整个复杂表达式的值还是 true

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

使用括号来明确优先级 (Explicit Parentheses)

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

    let enteredDoorCode = true
    let passedRetinaScan = false
    let hasDoorKey = false
    let knowsOverridePassword = true
    if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
        print("Welcome!")
    } else {
        print("ACCESS DENIED")
    }
    // 输出结果 Welcome!

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

推荐阅读

目录