Swift从零开始 - 运算符

69 阅读9分钟

运算符是一种特殊符号,可与一个或多个值共同使用以达到某种计算结果。

根据对操作目标个数的不同, 可以分为:

  • 一元运算符:如 !a, a!
  • 二元运算符:如 a+b,a*b
  • 三元运算符:如 a ? b : c

根据运算符的位置不同,也可以分为:

  • 前缀运算符:如 !a
  • 中缀运算符:如 a+b, a*b
  • 后缀运算符:如 a!

Swift在支持C中的大多数标准运算符的同时也增加了一些排除常见代码错误的能力。

  • 赋值符号(=) 不会返回值, 以防止它被误用于判等符号(==)的意图上。
  • 算数符号(+,-,*,/,% 以及其他)可以检测并阻止值溢出,以免在操作比存储类型允许的范围更大或者更小的数字时出现异常。

赋值运算符

作用是将一个值赋给另外一个值。如果赋值符号右侧是拥有多个值的元组,它的元素将会一次性地拆分成常量变量

与OC不同, Swift中 赋值运算符不会有返回值

OC:
NSInteger a = 1;
NSInteger b = 0;
if (b == a) {
    //do something
}

if (b = a) {
    //do something
}

在OC中, 上面两个判断都会进入执行体,而显然 第二个判断并不是期待执行的。

Swift:
var a = 1
var b = 0
if (b == a) {
    //do something
}

if (b = a) {     // error 
    //do something
}

在Swift中, 第二个判断是会抛出异常的

算术运算符

标准算术运算符

标准算术运算符包括: +,-,*,/,% 等, 默认情况下, Swift算术运算符不允许值溢出。同时, 在Swift中加号运算符(+)也是支持字符串的拼接操作的。

余数运算符 %
(a % b)可以求出余数
当a是负数时,结果与a的符号相同。 当b是负数时,可以将b的负号忽略,这意味着 (a % b)与 (a % -b)的结果是相等的。

一元算术运算符

数字值的正负号可以用前缀(-)来切换.
一元减号运算符(-)直接放置在要操作值的前面,不加任何空格。
一元加号运算符(+)直接返回它的操作值, 不会对其进行任何修改。

溢出运算符

在默认情况下, 当向一个整数赋予超于它容量的值时,Swift会报错而不是生成一个无效的数,以提供数据保护的安全性。
同时,Swift也提供了三个算术溢出运算符 来 让系统支持整数溢出运算。

  • &+ : 溢出加法
  • &- : 溢出减法
  • &* : 溢出乘法
let num1:UInt8 = 250
let num2 = num1 + 6 //error: Execution wasinterreupted,reason:EXC_BAD_INSTRUCTION...

let num3 = num1 &+ 6
print(num3)        // 1
  • 无符号数值向上或向下溢出

Screenshot 2023-12-05 at 21.35.33.png

  • 有符号数值向下溢出

Screenshot 2023-12-05 at 21.40.50.png

不管是有符号还是无符号的整型数值,当出现上溢的时候,它的值将由它所表示的最大值变成所能表示的最小值;同理,当出现下溢时, 则由所能表示的最小数变成最大数。

合并空值运算符

用两个问号(??)来表示,用于可选项(Optional)类型.
(a ?? b)它表示:如果可选项 a 有值则展开, 是nil,则返回默认值 b。

在 Swift 规范里, 表达式 a 必须是一个可选项, 表达式b 必须与 a 存储的类型相同。(虽然实际不按规范也能正常返回)

// 方式一: 强制解包
func addTwoNum(num1:Int?, num2:Int?) -> Int {
    // 强制解包,如果某一值为nil 则会crash
    return num1! + num2!
}

// 方式二: 判断方式
func addTwoNum(num1:Int?, num2:Int?) -> Int {
    if num1 != nil {
        if num2 != nil {
            return num1! + num2!
        } else {
            return num1!
        }
    } else {
        if num2 != nil {
            return num2!
        } else {
            return 0
        }
    }
}
// 方式三: 合并空值运算符
func addTwoNum(num1:Int?, num2:Int?) -> Int {
    return (num1 ?? 0) + (num2 ?? 0)
}

如果 a 的值是非空的,b 的值将不会考虑,也就是合并空值运算符是短路的。

// 短路
func getNum() -> Int {
    print("getNum")
    return 10
}

let a : Int? = 20
print(a ?? getNum()) // 打印20, 而不会打印getNum,因为a非空, 所以getNum是短路的!

区间运算符

闭区间运算符

使用三个点来表示 :(a...b),它定义了a到b的一组范围,且包含a和b,a的值不能大于b。

for index in 1...5 {
    print("\(index)")
}
// 1,2,3,4,5

半开区间运算符

使用 ..< 表示: (a..<b),定义了a到b的一组范围,包含a但不包含b,a的值不能大于b。如果a和b的值相等,那么返回的区间将是空的。

let person = ["zhang", "wang", "li", "zhao"]
let count = person.count
for p in 0..<count {
    print("\(p)")
}
// "zhang","wang", "li", "zhao"

单侧区间运算符

闭区间有另外一种形式,能让区间朝一个方向尽可能地远,这种区间叫做单侧区间,使用省略一侧区间的写法来表示。
半开区间也同样有单侧形式,只需要写它的最终值

let person = ["zhang", "wang", "li", "zhao"]
for p in person[...2] {
    print("\(p)")
}
// "zhang","wang", "li"

for p in person[1...] {
    print("\(p)")
}
// "wang", "li", "zhao"

for p in person[..<2] {
    print("\(p)")
}
// "wang", "li"

单侧区间也可以在其上下文中使用,而不仅仅是下标
可以遍历省略了最终值的单侧区间,但不能遍历省略了第一个值的单侧区间,因为遍历不知从何处开始。

let range = ...5
range.contains(7) //false
range.contains(5) //true
range.contains(-2) //true

倒序索引

使用reversed()方法,可以将一个正序循环变成倒序循环.

for index in (0...5).reversed {
    print("\(index)")
}
// 5, 4, 3, 2, 1, 0

区间运算符作用于字符串

例如字符串索引区间,删除指定区间字符

var str = "hello, world!"
let range = str.index(str.endIndex,offsetBy: -7)..<str.endIndex
tr.removeSubrange(range)
// "hello"

区间运算符作用于 Comparable

区间运算符可以作用于 Comparable 类型上,返回闭区间或半闭区间。

let str = "hello, world!"
let interval = "a"..."z"
for c in str {
    if !interval.contains(String(c)) {
        print("\(c) is not lower character")
    }
}

// ", is not lower character"

更多其他运算符,请查阅 文档

运算符的优先级和结合性

运算符优先级: 定义运算符的优先级, 使得一些运算符优先于其他运算符,高优先级的运算符会优先被计算。
运算符结合性: 定义了具有相同优先级的运算符是如何结合(或关联)的,是与左边结合为一组,还是与右边结合为一组。

举个例子:

1⃣️ 计算 2 + 3 % 4 * 5, 按照运算符的优先级计算,应该先计算 3 % 4, 再将其结果 * 5, 最后加 2. 最终值为17.

2⃣️ 逻辑运算符 && 和 || 是左相关的, 这意味着多个逻辑运行符共存的表达式会首先计算最左侧表达式。

运算符重载

在Swift中, 类和结构体是可以为现有运算符提供自定义的实现的,这称为运算符重载

struct Vector2D {
    var x = 0.0, y = 0.0
}

extension Vector2D {
    static func + (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(left.x + right.x, left.y + right.y)
    }
}

let vector = Vector2D(x: 3.0, y: 4.0)
let vector1 = Vector2D(x: 1.0, y: 3.0)
let resultVector = vector + vector1
print(resultVector) // x:4.0, y:7.0

一元运算符重载

类与结构体也能提供标准一元运算符的实现.
要实现前缀或后缀运算符,需要在声明运算符函数的时候在func前指定prefix或者postfix限定符。

struct Vector2D {
    var x = 0.0, y = 0.0
}

extension Vector2D {
    staic prefix func - (vector : Vector2D) -> {
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}

let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
print(negative)  // x:-3.0,y:-4.0
let revert = -negative
print(revert)  // x:3.0,y:4.0

组合赋值运算符的重载

组合赋值运算符将赋值运算符(=)与其他运算符进行组合
在实现的时候,需要将运算符的左参数设置成inout类型,因为这个参数会在运算函数内直接被修改

struct Vector2D {
    var x = 0.0, y = 0.0
}
 
extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) -> {
        left = left + right // 前提已经实现了上面的加法重载
    }
}

var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)

original += vectorToAdd
print(original) // x:4.0, y:6.0

等价运算符的重载

自定义类和结构体 不接收等价运算符的默认实现( ==!=)。
要使用等价运算符来检查自己类型的等价,需要提供一个 == 运算符重载,并遵循标准库的 Equatable 协议。

struct Vector2D {
    var x = 0.0, y = 0.0
}

extension Vector2D : Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
}

let vector = Vector2D(x: 3.0, y: 4.0)
let vector1 = Vector2D(x: 3.0, y: 4.0)
print(vector==vector1) //true

Swift为以下自定义类型提供等价运算符合成实现:

  • 只拥有遵循 Equatable 协议存储属性(没有计算属性)的结构体
  • 只拥有遵循 Equatable 协议关联类型的枚举
  • 没有关联类型的枚举

所以对于上例, 如果使 Vector2D 遵循 Equatable 协议, 即使不重载等价运算符,也可以达成目的.

struct Vector2D : Equatable {
    var x = 0.0, y = 0.0
}

let vector = Vector2D(x: 3.0, y: 4.0)
let vector1 = Vector2D(x: 3.0, y: 4.0)
print(vector==vector1) //true

自定义运算符

在Swift中,除了可以实现标准运算符, 还可以声明和实现自定义运算符。
要实现新的运算符,需要在全局全局作用域内,使用operator关键字进行声明,同时还要指定 prefix,infix或者postfix限定符

struct Vector2D : Equatable {
    var x = 0.0, y = 0.0
}

prefix operator +++   //自我翻倍

extension Vector2D {
    static func +++ (vector: inout Vector2D) -> Vector2D {
        vector += vector
        return vector
    }
}

var tobeDouble = Vector2D(x:1.0, y:4.0)
+++tobeDouble  // x:2.0,y:8.0

自定义优先级和结合性

自定义的中缀(infix)运算符也可以指定优先级及结合性
每一个自定义的中缀运算符都属于同一个优先级组
优先级组指定了自定义中缀运算符和其他中缀运算符的关系。

OperatorsExamples
BitwiseShiftPrecedence>> <<
MultiplicationPrecedence% * /
AdditionPrecedence- + - ^ ``
RangeFormationPrecedence..< ...
CastingPrecedenceis ,asas?, 和 as!
NilCoalescingPrecedence??
ComparisonPrecedence!= > < >= <= === ==
LogicalConjunctionPrecedence&& .&
LogicalDisjunctionPrecedence`. . .^`
TernaryPrecedence? :
Assignment​Precedence`= %= /= *= >>= <<= ^= += -=`

更多运算符优先级内容,请查阅 文档

precedencegroup MyCustomPrecedence {
    associativity: left
    lowerThan : AdditionPrecedence
    //growerThan: MultiplicationPrecedence
}
infix operator +- : AdditionPrecedence

extension Vector2D {
    static func +- (left:Vector2D,right:Vector2D) -> Vector2D {
        return(Vector2D(x:left.x+right.x, y:left.y-right.y))
    }
}

infix operator *^ : MultiplicationPrecedence
precedencegroup MyCustomPrecedence {
    associativity: left
    lowerThan : AdditionPrecedence
}

extension Vector2D {
    static func *^ (left:Vector2D,right:Vector2D) -> Vector2D {
        return(Vector2D(x:left.x+right.x, y:left.y*left.y+right.y*right.y))
    }
}

let firstVector = Vector2D(x:1.0, y:2.0)
let secondVector = Vector2D(x:3.0,y:7.0)
let plusMinusVector = firstVector +- secondVector // (x:4.0, y: -5.0)

let thirdVector = Vector2D(x:2.0,y:2.0)

// 如果 *^ 优先组是MultiplicationPrecedence, 则先计算 *^, 再计算 +-
// 如果 *^ 使用 MyCustomPrecedence 优先组,则先计算 +-, 再计算 *^
let vector = firstVector +- secondVector *^ thirdVector