Swift 5.0语法补充

162 阅读9分钟

for-in循环

  • i默认值是let,有需要时可以声明为var
//在一次循环中,若没有指定ivar,则不可以对i进行赋值等操作

  for var i in 1...3 {

  i += 5

  print(i)

  }
  • 单侧区间与数组结合

  for name in names[2...] {

  print(name)

  }// Brain Jack

  for name in names[...2] {

  print(name)

  }// Anna Alex Brian
  • case判断语句后可加where 来限制判断条件

switch

  • fallthtough贯穿效果 
  • 绑定值

    switch point {

    case (let x ,0):

    print("on the x-axis with a \(x) value")

    case (0 , let y)

    print("on the y-axis with a \(y) value")

    case let (x,y)

    print("somewhere else at (\(x),\(y))")

    }//必要时let也可以改为var

函数

  • 隐式返回(函数体是单一表达式,则函数会隐式返回这个表达式)

  v1 + v2

  }

  sum(v1: 20, v2: 30)//50
  • 利用元组可实现多返回值

  • 函数的文档注释

  ///求和【概述】

  ///

  ///将2个整数相加【更详尽的描述】

  ///

  /// - Parameter v1: 第一个数

  /// - Prarmeter v2: 第二个数

  /// - Returns:2个整数的和

  ///

  /// - Note:传入2个整数即可【批注】

  ///

func_zhushi.png

  • 默认参数值

在c++等语言中,如果函数要设置默认参数值,规定必须从右向左设置默认参数值,如果不这样的话,在函数调用的过程中可能会出现参数值传递混乱的问题。Swift通过设置外部参数名(参数标签)比较成功的避免了这个问题,但是在一些特定情况下(比如,省略参数标签)也会遇到问题参数值传递混乱的问题。

    func test(_ first: Int = 10, middle: Int, _ last: Int =  30){

    

    }

    test(middle:20)

    //这里第二个参数不能省略参数标签,否则编译器会报错,会将20赋值给第一个参数
  • 可变参数。一个函数最多只能有一个可变参数,紧跟在可变参数后面的参数不能省略参数标签
    func test(_ numbers: Int..., string: String, _ other: String) {

    }

    test(10,20,30, string: "Jack", "Rose")
  • print函数
    public func print(_ items: Any..., separator: String = "", trminator: String = "\n")

    

    print(1,2,3,4,5, separator: "_") // 1_2_3_4_5

输入输出参数(in-out paramet)

用inout定义一个参数,可以在函数内部修改外部变量的值

注 1.可变参数不能标记为inout

​    2.inout参数不能由默认值

​ 3.inout参数只能传入可以被多次赋值的

​ 4.inout参数的本质是地址传递(引用传递)

    func swapValues(_ v1: inout Int, _v2: inout Int) {

        (v1,v2) = (v2,v1)

    }

函数重载

函数重载要求函数名相同,参数个数不同||参数类型不同||参数标签不同

注:1.返回值类型与函数重载无关,不能凭借函数返回值不同而构造函数重载

​ 2.默认参数值与函数虫灾一起使用产生二义性时,编译器并不会报错

    func sum(v1: Int, v2: Int) -> Int {
        v1 + v2
   }
   
    func sum(v1: Int, v2: Int, v3: Int = 10) {
        v1 + v2 + v3
    }
    //会调用sum(v1: Int, v2: Int)
    sum(v1: 10, v2: 20)

​ 3.可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错

    func sum(v1: Int, v2: Int) -> Int {
        v1 + v2
    }
    
    func sum(v1: Int, _ v2: Int) -> Int {
        v1 + v2
    }
    
    func sum(_ numbers: Int...) -> Int {
        var total = 0
        for number in numbers {
            total += number
            return total
        }
    }
    // error: ambiguous ues of 'sum'
    sum(10,20)

typealias

  • typealias用来给类型起别名

typealias Byte = Int8

typealias Short = Int16

typealias Long = Int64

枚举的关联值

枚举类型通常有原始值,又是也会将枚举的成员变量与其他关联的值关联存储起来。一般来说,一个枚举的实例只占一个字节的内存空间,如果关联了其他类型的值,那么该枚举实例占用的内存大小为:占用内存空间最大的成员变量的字节大小+1个字节(无关联值的枚举实例的大小)。注意,原始值不占用枚举变量的内存。

MemoryLayout.size //实际用到的内存大小

MemoryLayout.stride //分配的内存大小

MemoryLayout.alignment //内存对齐方式

enum Passwod {

case number(Int, Int, Int, Int)

case other

}

MemoryLayout<object>.size //33 = 32 + 1

MemoryLayout<object>.stride //40(对齐方式是8,(33/8 +1)*8 = 40)

MemoryLayout<object>.alignment //8

查看内存内容:Debug -> Debug Workflow -> View Memory -> 内存地址

可选值解包

if let并列解包多个可选值:if let a = optional(objcet1), let b = objectionable(object2) {   }

if let c = a, let d = b {

print(c,d)

}

// <=> if a != nil && b != nil

空合并运算符  a ?? b 

前提:a是可选值,b是可选值或者不是可选值,但是b的存储类型必须和a相同

结果:如果a不是nil,则返回a

​ 如果a是nil,则返回b

​ 如果b不是可选值,那么返回a时会自动解包

let a: Int? = 1

let b: Int? = 2

let c = a ?? b //c是Int?,Optional(1)

let a: Int? = nil

let b: Int? = 2

let c = a ?? b //c是Int?,Optional(2)

let a: Int? = nil

let b: Int? = nil

let c = a ?? b //c是Int?,nil

let a: Int? = 1

let b: Int = 2

let c = a ?? b //c是Int,1

let a: Int? = nil

let b: Int = 2

let c = a ?? b //c是Int,2

??与if let配合使用

let a: Int? = nil

let b: Int? = 2

if let c = a ?? b {
    print(c)
}

//只要a ?? b不是nil就可以执行print(c),也就相当于if a != nil || b != nil

guard语句

当guard语句的条件为false时,就会执行else括号中的代码;当guard语句的条件为true时,就会跳过guard语句;guard语句特别适合用来”提前退出“。当使用guard语句进行可选值绑定时,绑定的常量、变量也能在外层作用域中使用。

guard ... else {

//code

//return break contiune throw error

}

结构体内存结构

//编译器会为所有结构体自动生成初始化器,有时会根据情况生成多个初始化器,其宗旨是:保证所有成员都有初始值。

struct Point {

    var x: Int = 0

    var y: Int = 0

    var origin: Bool = false

}

MemoryLayout<Point>.size //17

MemoryLayout<Point>.stride //24

MemoryLayout<Point>.alignment //8

闭包表达式

{

(参数列表) -> 返回值类型 in

函数体代码

}

闭包(Closure)

定义:一个函数和它所捕获的变量/常量环境组合起来,成为闭包。一般指定义在函数内部的函数,通常情况下捕获的是外层函数的局部变量/常量。

可以把闭包想象成是一个类的实例对象,内存在堆空间,捕获的局部变量/常量就是对象的成员(存储属性),组成闭包的函数就是类内部定义的方法。

typealias Fn = (Int) -> (Int, Int)

func getFns() -> (Fn, Fn) {

    var num1 = 0

    var num2 = 0

    func plus(_ i: Int) -> (Int, Int) {

        num1 += i

        num2 += i << 1

        return (num1, num2)

    }

    func minus(_ i: Int) -> (Int, Int) {

        num1 -= i

        num2 -= i << 1

        return (num1, num2)

    }

    return (plus, minus)

}

let (p, m) = getFns()

p(5) //(5, 10)

m(4) //(1, 2)

p(3) //(4, 8)

m(2) //(2, 4)

属性

属性分为存储属性和计算属性

存储属性

*** 类似于成员变量的概念

存储在实例的内存中

结构体、类可以定义存储属性

枚举不可以定义存储属性

计算属性

本质就是方法(函数)

不占用实例的内存

枚举、结构体、类都可以定义计算属性

计算属性:

1.set传入的新值默认叫做newValue,也可以自定义

2.可定义只读计算属性,定义计算属性时只能用var,不能用let

3.计算属性的值是可能发生变化的(即使是只读计算属性),而let代表常量,所定义的值是一成不变的

**************************************************

struct Circle {

    var radius: Double

    var diameter: Double {

        set(newDiameter) {

            radius = newDiameter / 2

        }

        get {

            radius * 2

        }

    }

}

只读计算属性

struct Circle {

    var radius: Double

    var diameter: Double {

        get {

        radius * 2

        }

    }

}

枚举rawVaule的原理:枚举的原始值其实就是只读计算属性

延迟存储属性(Lazy Stored Property)

使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化

class Car {
    init() {
        print("Car init!")
    }
    func run() {
        print("Car is running!")
    }
}
class Person {
    lazy var car = Car()
    init() {
       print("Person init!")
    }
    func goOut() {
       car.run()
    }
}
let p = Person()
print("---------")
p.goOut()
//打印结果:
Person init!
----------
Car init!
Car is running!

lazy属性必须是var,不能是let,let必须在实例的初始化方法完成之前就拥有值;如果多条线程同时第一次访问lazy属性,无法保证属性值被初始化一次(非线程安全)。

当结构体包含一个延迟存储属性时,只有var才能访问延迟存储属性,因为延迟属性初始化时需要改变结构体的内存。

属性观察器

在开发中,可以为非lazy的var存储属性设置属性观察器

struct Circle {
    var radius: Double {
        willSet {
            print("willSet", newValue)
        } 
        didSet {
            print("didSet",oldValue, radius)
        }
    }
   init() {
      self.radius = 1.0 
      print("Circle init!")
   }
}

_ 没有弄清楚为什么计算属性不可以设置属性观察器!!

inout再次探究

struct Shape {

    var width: Int

    var side: Int {

        willSet {

            print("willSetSide", newValue)

        }

        didSet {

            print("didSetSide",oldValue, side)

        }

    }

    var girth: Int {

        set {

            width = newValue / side

                print("setGirth", newValue)

        }

        get {

            print("getGirth")

            return width * side

        }

    }

    func show() {

        print("width=\(width), side=\(side), girth=\(girth)")

    }

}

    func test(_ num: inout Int) {

        num = 20

    }
    
var s = Shape(width: 10, side: 4)

test(&s.width)

s.show()

print("------------")

test(&s.side)

s.show()

print("------------")

test(&s.girth)

s.show()

//下面是打印结果

getGirth

width=20, side=4, girth=80

------------

willSetSide 20

didSetSide 4 20

getGirth

width=20, side=20, girth=400

------------

getGirth

setGirth 20

getGirth

width=1, side=20, girth=20

总结

如果实参有物理内存地址,且没有设置属性观察器,那么inout直接将实参的内存地址传入函数(实参进行引用传递)

如果实参是计算属性或者设置了属性观察器 ,那么inout则采取Copy In Copy Out的做法

1.调用该函数时,先复制实参的值,产生副本【get】

2.将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值

3.函数返回后,再将副本的值覆盖实参的值【set】

inout的本质就是引用传递(地址传递)

类型属性(Type Property)

严格来说,属性可以分为:

1、实例属性(Instance Property):只能通过实例去访问

  • 1.存储实例属性(Stored Instance Property):存储在实例的内存中,每个实例都有1份
  • 2.计算实例属性(Computed Instance Property)

2.类型属性(Type Property):只能通过类型去访问

  • 1.存储类型属性(Stored Type Property):整个程序运行过程中,就只有1份内存(类似全局变量)
  • 2.计算类型属性
  • 可以通过static定义类型属性,如果实类,也可以用关键字class

struct Car {

    static var count: Int = 0

    init() {

        Car.count += 1

    }

}

let c1 = Car()

let c2 = Car()

let c3 = Car()

print(Car.count) //3

注:1.不同于存储实例属性,必须给存储类型属性设定初始值,因为类型没有像实例那样的init初始化器来初始化存储属性。

​        2.存储类型属性默认就是lazy,在第一次使用时初始化,就算被多个线程同时访问,保证只会初始化一次,存储类类型可以是let

​ 3.枚举类型也可以定义类型属性

方法

实例方法(Instance Method):通过实例对象调用

类型方法(Type Method):通过类型调用,用static或者class关键字定义

结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改。想要修改的话需要在func关键字前加mutating。

在func前面加@discardableResult,可以消除函数调用后返回值未被使用的警告。