Swift-特殊关键字、特殊语句

1,032 阅读8分钟

1.特殊关键字

1.1 fallthrough

Switch的case默认不贯穿执行的,所以语句结尾是不需要添加break关键字。但有时需求需要能继续贯穿,此时需要在case句尾添加fallthrough关键字。

🌰:

var num = 1
switch num{
case 1 :
    print("number is 1")
    fallthrough
case 2:
    print("number is 2")
case 3:
    print("number is 3")
default:
    print("number is other") //如果不想做任何事情,加个break就可以。
}
//打印结果:
//number is 1
//number is 2

注意:
1).fallthrough后面的case不管满不满足条件都会执行;
2).只执行fallthrough后面一个case
3).只有fallthrough所在case满足条件时,才会执行fallthrough

1.2 区间运算符

1.2.1 闭区间运算符(...)

... :闭区间运算符;使用格式为:n ... m (n、m为Int类型,n < m );
等价于:n <= x && x <= m

var n = 10
var m = 20
var arr : [Int= []
forin n ... m{
    arr.append(i)
}
print(arr)
//打印结果:[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

1.2.2 半开区间运算符

n..<m 等价于 a <= x && x < m
n..>m 等价于 m <= x && x < n

1.2.3 单侧区间运算符

n.. 等价于a <= x
..m 等价于 x <= m

1.3 输入输出参数-inout

主要使用场景:当外部实参的作为参数传入函数中时,内部参数改变,外部实参也跟着改变;
主要形式为:参数name : inout 参数类型

举个🌰:

func swapValues(_ v1:inout Int,_ v2 : inout Int){
    (v1,v2) = (v2,v1)//使用元组交换两个参数的值。
}
var number1 = 10
var number2 = 20
swapValues(&number1, &number2)
print("number1 is \(number1),number2 is \(number2)")
//打印结果:number1 is 20,number2 is 10

注意:
1)可变参数不能标记为inout;
2)inout参数不能有默认值;
3)inout参数只能传入可以被多次赋值的;
4)inout参数的本质是地址传递(引用传递);

1.4 递归枚举-indirect

递归枚举:枚举成员使用了枚举本身作为关联值,可以在枚举中调用自己,达到一种链式表达式,使用indirect关键字修饰;
具体使用参照:Swift-枚举

1.5 mutating

1).结构体、枚举中声明方法内是不可以修改属性变量,在方法前加mutating后,才可以修改。
2).mutating修饰代理方法,实现了Protocol的结构体、枚举,在实现代理方法后,可以在代理方法内部修改本身的属性变量。

1.6 autoclosure-自动闭包

自动闭包是一种自动创建的,用来把作为实际参数传递给函数的表达式打包的闭包。它不接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值。
这个语法的好处在于通过写普通表达式代替显示闭包而使你省略函数形式参数的括号。
举个🌰:

//没有添加@autoclosure
func getFirstPositive(_ v1:Int,_ v2:()->Int) -> Int?{
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4) {20}

//添加@autoclosure后
func getFirstPositive(_ v1:Int,_ v2:@autoclosure ()->Int) -> Int?{
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20)

附加:
@autoclosure只支持()-> T格式的参数;
空合并运算符??使用了@autoclosure技术;
有@autoclosure、无@autoclosure,构成了函数重载;

1.7 final

1)final修饰的方法、下标、属性,不能被重写;
2)final修饰的类,不能被继承。

1.8 required

1)用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现);
2)如果子类重写了required初始化器,也必须加上required,不用加override。

1.9 溢出运算符(Overflow Operator)

算数运算符出现溢出时会抛出运行时错误,使用溢出运算符(&+、&-、&*)用来支持溢出运算;
1)&+:当运算结果大于算数运算符最大值时,超出部分从最小值开始递增。
举个🌰:

var v1 = UInt8.max //0-255
print("v1=",v1)

var v2 = v1 &+ 1 //255+1 = 256;超过1个
print("v2=",v2)//打印结果:v2= 0

var v3 = v1 &+ 3//255+3 = 258;超过三个
print("v3=",v3)//打印结果:v2= 2

2)&-:当运算结果小于算数运算符最小值时,从最大值开始递减。

var v1 = UInt8.min //255

print("v1=",v1)

var v2 = v1 &- 1

print("v2=",v2)//打印结果:v2= 255

var v3 = v1 &- 3

print("v3=",v3)//打印结果:v2= 253

3)&*:当运算结果大于算数运算符最大值时,从最小值开始递增。

1.9 Equatable

遵守Equatable协议,可以判断两个实例是否等价,相当于重载==运算符,与此同时,自动也重载了!=运算符;

struct Point:Equatable{
    var x : Int,y : Int
}
var p1 = Point(x:10,y:20)
var p2 = Point(x: 11, y: 22)
print(p1 == p2)//打印结果:true
print(p1 != p2)//打印结果:false

Swift为以下类型提供默认的Equatable实现:
1)没有关联类型的枚举;
2)只拥有遵守Equatable协议关联类型的枚举;
3)只拥有遵守Equatable协议存储属性的结构体;

引用类型比较存储的地址值是否相等(是否引用同一个对象),使用恒等运算符===!==

1.10 Comparable

要想比较2个实例的大小,一般做法是:
1)遵守Comparable协议;
2)重载相应的运算符;
Comparable源码如下:

public protocol Comparable : Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool

    static func <= (lhs: Self, rhs: Self) -> Bool

    static func >= (lhs: Self, rhs: Self) -> Bool

    static func > (lhs: Self, rhs: Self) -> Bool
}

遵守Comparable协议后,需要实现相应的函数,自己定义比较规则,不然会报错;并不像Equatable一样。

1.11 open & public & internal & fileprivate & private

访问级别从高到低;
1)open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上);
2)public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写;
3)internal:只允许在定义实体的模块中访问,不允许在其他模块中访问;
4)fileprivate:只允许在定义实体的源文件中访问;
5)private:只允许在定义实体的封闭声明中访问;

总结:
1):的右边的访问级别高于左边;适用于属性、类、继承、扩展等情况;
2)被引用的访问级别高于引用的;

2.特殊语句

2.1 标签语句

标签语句格式:标签名: for-in
标签语句好处是:可以设计停止外部循环时机;或者跳过当前循环。 举个🌰:

outer: forin 1...4{
    for k in 1...4{
        if k == 3{
            continue outer//跳过当前循环
        }
        if i == 3{
            break outer//停止外部循环
        }
        print("i = \(i) k = \(k)")
    }
}
//当k = 3 时跳过当前循环,进行下个循环
//当i = 3 时停止外部循环(也就是停止全部循环)

2.2 Switch-case特殊情况

举个🌰:

var age : Int? = 10
switch age {
    case let v:
     print("1",v)
    case nil:
     print("2")
}

截屏2022-01-12 上午9.23.36.png 此时,代码显示警告的,主要是因为case let v此时是直接将age的值赋值给v; 所以添加case nil:才会报警告;v报警告是因为此时的vInt?类型,打印的时候需要解包或者写为Any 。 可以将上面的代码修改为下面代码就不会报错了:

var age : Int? = 10
switch age {
    case let v:
     print("1",v!)//或者 print("1",v as Any)
}

再看另一个例子:此时将上面的代码修改成下面的格式,就不会报错警告了,但是此时代码意义变了;

var age : Int? = 10
switch age {
    case let v?:
     print("1",v)
    case nil:
     print("2")

}

等价于:

if let v = age {
    print("1",v)
}else{
    print("2")
}

2.3 运算符重载

举个🌰:

struct Point{
    var x : Int,y : Int
    
    //加运算符
    static func + (p1 : Point,p2 : Point) -> Point{
        Point(x: p1.x+p2.x, y: p1.y+p2.y)
     }
     
    //减运算符
    static func - (p1 : Point,p2 : Point) -> Point{
        Point(x: p1.x-p2.x, y: p1.y-p2.y)
     }
     
    //取负运算符
    static prefix func - (p1 : Point) -> Point{
        Point(x: -p1.x, y: -p1.y)
     }
     
    //+= 运算符
    static func += (p1: inout Point,p2:Point){
        p1 = p1 + p2
    }

    //++i
    //prefix: 前置运算符
    static prefix func ++ (p : inout Point) -> Point{
        p += Point(x: 1, y: 1)
        return p
    }
    
    //i++
    //postfix: 后置运算符
    static postfix func ++ (p : inout Point) -> Point{
        let tmp = p
        p += Point(x: 1, y: 1)
        return tmp
    }

    //重载比较运算符
    //相当于继承了Comparable协议
    // < 
   static func < (p1 : Point,p2 : Point) -> Bool{
       p1.x > p2.x || p1.x == p2.x && p1.y > p2.y
    }
    
    //相当于继承了Equtable协议
    //重载==、!=运算符
    static func == (p1:Point,p2:Point) -> Bool{
        p1.x == p2.x && p1.y == p2.y
    }

    static func != (p1:Point,p2:Point) -> Bool{
        p1.x != p2.x || p1.y != p2.y
    }
}

var p1 = Point(x:10,y:20)
var p2 = Point(x: 11, y: 22)
print(p1 < p2)
print(p1 == p2)

2.4 自定义运算符

自定义新的运算符:在全局作用域使用operator进行声明

prefix operator 前缀运算符
postfix operator 后缀运算符
infix operator 中缀运算符 : 优先级组
前缀运算符🌰:

prefix operator +++
struct Point{
    var x : Int,y : Int
    static prefix func +++ (point : inout Point ) -> Point{
        point = Point(x: point.x + point.x + point.x , y: point.y + point.y + point.y)
        return point
    }
}
var p = Point(x : 10,y : 10)
print(+++p)//打印结果:Point(x: 30, y: 30)

后缀运算符🌰:

postfix operator +++
struct Point{
    var x : Int,y : Int
    static postfix func +++ (point : inout Point ) -> Point{
        let temp = point
        point = Point(x: point.x + point.x + point.x , y: point.y + point.y + point.y)
        return temp
    }
}
var p = Point(x : 10,y : 10)
print(p+++)//打印结果:Point(x: 10, y: 10)
print(p)//打印结果:Point(x: 30, y: 30)

中缀运算符🌰:

infix operator +- : PlusMinusPrecedence

//优先级组的定义
precedencegroup PlusMinusPrecedence{

    associativity : none

    higherThan : AdditionPrecedence

    lowerThan : MultiplicationPrecedence

    assignment : true

}

struct Point{

    var x : Int,y : Int

    static func +- (left:Point,right:Point) -> Point{

        return Point(x: left.x + right.x, y: left.y - right.y)

    }

}

var p1 = Point(x: 10, y: 10)

var p2 = Point(x: 10, y: 5)

print(p1 +- p2)//打印结果:Point(x: 20, y: 5)

中缀运算符的优先级组定义格式如下:

precedencegroup 优先级组名{
    associativity : none/left/right
    higherThan : 优先级高于xx
    lowerThan : 优先级低于xx
    assignment : Bool
}

associativity:中缀运算符的运算顺序;left-从左至右;right-从右至左;none-不支持多个结合使用,一次只能用一个;
assignment:true-拥有跟赋值运算符一样的优先级; higherThan/lowerThan:可以参考下面的网址。Operator

2.5 使用访问控制符,限制写权限(或:限制属性只读,不能写的方法)

getter、setter访问级别默认与属性一致;可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限;
举个🌰:

fileprivate(set) public var num = 10

//Person默认访问级别为internal
class Person {
    //age: getter访问级别为internal,setter访问级别为private;写法与下面的等价
    private(set) var age = 0
    //weight:getter访问级别为public,setter访问级别为fileprivate
    fileprivate(set) public var weight: Int {
        set { }
        get { 10 }
    }
    internal(set) public subscript(index: Int) -> Int {
        set {age = newValue}
        get { index }
    }
}

var p = Person()

p.age = 100//此时报错,访问级别不允许赋值

print(p.age)