美文网首页
Swift 之流程控制、函数、枚举、可选类型、结构体和类、闭包

Swift 之流程控制、函数、枚举、可选类型、结构体和类、闭包

作者: Abner_XuanYuan | 来源:发表于2023-10-22 15:42 被阅读0次

    1、流程控制

    1、if-else

    1、if 后面的条件只能是 bool 类型。
    2、if 后面的条件可以省略小括号,但条件后面的大括号不可以省略。

    2、while

    1、swift 中的 while 用法和 OC 类似。
    2、repeat-while 相当于 C 语言中的 do-while。

    3、for

    闭区间运算符:a...b 即取值大于等于 a 且小于等于 b。
    半开区间运算符:a..<b 即取值大于等于 a 且小于 b;a<..b即取值大于 a 且小于等于 b。
    单侧区间:[a...]即取值从 a 到无限大;[..<a]即取值从无限小到小于 a。
    区间类型:

    let range1: ClosedRange<Int> = 1...3
    let range2: Range<Int> = 1..<3
    let range3: PartialRangeThrough<Int> = ...5
    

    字符、字符串:字符、字符串也能使用区间运算符,但默认不能用在 for-in 中。

    //表示 str1 包含 a,b,c,d,e,f 
    let str1 = "a"..."f"  
    
    /*
    表示 str2 包含如下:
    第一个字符包含 c 到 f。
    第二个字符有以下情况:
        第一个字符为 c 是,第二个字符为 c 到 z;
        第一个字符为 f 时,第二个字符为 a 到 f;
        否则第二个字符为 a 到 z。
    */
    let str2 = "cc"..."ff"  
    
    // \0到~囊括了所有可能要用到的ASCII字符
    let characterRange: ClosedRange<Character> = "\0"..."~"
    

    带间隔的区间值:

    // t 的取值:从4开始,累加2,不超过11
    for t in stride(from: 4, through: 11, by: 2) {
        print(tickMark) // 4 6 8 10
    }
    
    4、switch

    1、默认可以不写 break,并不会贯穿到后面的条件。
    2、使用 fallthrough 可以实现贯穿效果。
    3、switch 必须要保证能处理所有情况。
    4、case、default 后面至少要有一条语句,如果不想做任何事,加个 break 即可。
    5、如果所有的条件都已处理,也可以不必使用 default。
    6、switch 条件可以支持 Character、String、int、区间匹配、元组匹配。
    7、switch 条件可以支持复合条件,每个条件用 ,分开。
    8、值绑定

    let point = (2, 0)
    switch point {
    case (let x, 0):
        print("on the x-axis with an x value of \(x)")
    case (0, let y):
        print("on the y-axis with a y value of \(y)")
    case let (x, y):
        print("somewhere else at (\(x), \(y))")
    } 
    // on the x-axis with an x value of 2
    //必要时let也可以改为var
    
    5、where:用来添加过滤条件
    6、标签语句
    outer: for i in 1...4 {
        for k in 1...4 {
            if k == 3 {
                continue outer
            }
            if i == 3 {
                break outer
            }
            print("i == \(i), k == \(k)")
        }
    }
    

    2、函数

    函数的定义:func 函数名(参数类型) -> 返回值类型 {}

    形参默认是 let

    //无参且返回 int 类型
    func config() -> Int {
        return 20
    }
    
    //有参且返回 Int 类型
    func sum(v1: Int, v2: Int) -> Int{
        return v1 + v2
    }
    sum(v1: 10, v2: 5)
    

    隐式返回
    如果整个函数体是一个单表达式,那么函数会隐式返回这个表达式(隐藏 return)

    func sum(v1: Int, v2: Int) -> Int{
       //会将 v1+v2 的值返回
        v1 + v2
    }
    sum(v1: 10, v2: 5)
    

    返回元组:实现多返回值

    //计算两个数的和、差、平均值
    func sum(v1: Int, v2: Int) -> (sum: Int, difference: Int, average: Int){
        sum =  v1 + v2
        //sum >> 1:和右移一位就相当于除以2
        return (sum, v1-v2, sum >> 1)
    }
    
    let result = calculate(v1: 20, v2: 10)
    result.0 //30
    result.difference  //10
    result.2  //15
    

    函数文档注释:

        /// 求和【概述】
        ///
        /// 将2个整数相加【更详细的描述】
        ///
        /// - Parameter v1: 第1个整数
        /// - Parameter v2: 第2个整数
        /// - Returns: 2个整数的和
        ///
        /// - Note:传入2个整数即可【批注】
        ///
        func sum(v1: Int, v2: Int) -> Int {
            v1 + v2
        }
    

    参数标签

    func goToWork(at time: String) {
        print("this time is \(time)")
    }
    goToWork(at: "08:00")
    // this time is 08:00
    
    //省略参数标签
    func sum(_ v1: Int, _ v2: Int) -> Int {
        v1 + v2
    }
    sum(10, 20)
    

    默认参数值

    func check(name: String = "nobody", age: Int, job: String = "none") {
        print("name=\(name), age=\(age), job=\(job)")
    }
    check(name: "Jack", age: 20, job: "Doctor") // name=Jack, age=20, job=Doctor
    check(name: "Rose", age: 18) // name=Rose, age=18, job=none
    check(age: 10, job: "Batman") // name=nobody, age=10, job=Batman
    check(age: 15) // name=nobody, age=15, job=none
    //在省略参数标签时,需要特别注意,避免出错
    

    可变参数

    func sum(_ numbers: Int...) -> Int {
        var total = 0
        for number in numbers {
            total += number
        }
        return total
    }
    sum(10, 20, 30, 40) // 100
    // 紧跟在可变参数后面的参数不能省略参数标签
    
    // 参数 string 不能省略标签
    func test(_ numbers: Int..., string: String, _ other: String) { }
    test(10, 20, 30, string: "Jack", "Rose")
    

    输入输出参数 inout
    可以用 inout 定义一个输入输出参数:可以在函数内部修改外部实参的值

    func swapValues(_ v1: inout Int, _ v2: inout Int) {
        let tmp = v1
        v1 = v2
        v2 = tmp
    }
    var num1 = 10
    var num2 = 20
    swapValues(&num1, &num2)
    
    func swapValues(_ v1: inout Int, _ v2: inout Int) {
        (v1, v2) = (v2, v1)
    }
    

    注:
    1、可变参数不能标记为 inout
    2、inout 参数不能有默认值
    3、inout 参数只能传入可以被多次赋值的
    4、如果实参有物理内存地址,且没有设置属性观察器,直接将实参的内存地址传入函数(实参进行引用传递)。
    5、如果实参是计算属性或者设置了属性观察器,采取了 Copy In Copy Out 的做法。
    a、调用该函数时,先复制实参的值,产生副本【get】。
    b、将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值。
    c、函数返回后,再将副本的值覆盖实参的值【set】。
    总结:inout 的本质就是引用传递(地址传递)

    函数重载
    规则:函数名相同,参数个数不同或者参数类型不同或者参数标签不同。
    1、返回值类型与函数重载无关。
    2、默认参数值和函数重载一起使用产生二义性时,编译器并不会报错。
    3、可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错。

    内联函数
    内联函数可以将函数调用展开成函数体。如果开启了编译器优化(Release 模式默认会开启优化),编译器会自动将某些函数变成内联函数。
    1、哪些函数不会被自动内联?
    函数体比较长、包含递归调用、包含动态派发等。

    2、@inline

    // 永远不会被内联(即使开启了编译器优化)
    @inline(never) func test() {
        print("test")
    }
    
    // 开启编译器优化后,即使代码很长,也会被内联(递归调用函数、动态派发的函数除外)
    @inline(__always) func test() {
        print("test")
    }
    
    注:
    在 Release 模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用 @inline。
    

    函数可以作为参数、返回值,此时的函数称为高阶函数

    typealias 用来给类型起别名

    嵌套函数:将函数定义在函数内部。

    3、枚举

    1、基本用法
    enum Direction {
        case north
        case south
        case east
        case west
    }
    
    //等价于
    enum Direction {
        case north, south, east, west
    }
    
    2、关联值

    将枚举的成员值跟其他类型的值关联存储在一起。关联值是直接存储在枚举类型的内存中。

    enum Score {
        case points(Int)
        case grade(Character)
    }
    
    var score = Score.points(96)
    score = .grade("A")
    
    switch score {
    case let .points(i):
        print(i, "points")
    case let .grade(i):
        print("grade", i)
    } // grade A
    
    
    enum Date {
        case digit(year: Int, month: Int, day: Int)
        case string(String)
    }
    
    var date = Date.digit(year: 2011, month: 9, day: 10)
    date = .string("2011-09-10")
    switch date {
    case .digit(let year, let month, let day):
        print(year, month, day)
    case let .string(value):
        print(value)
    }
    
    3、原始值

    枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做:原始值。原始值的 case 值是固定的,不可以更改。原始值不占用枚举的内存,也就是不会存储到枚举变量的内存中。

    enum PokerSuit : Character {
      case spade = "♠"
      case heart = "♥"
      case diamond = "♦"
      case club = "♣"
    }
    
    4、隐式原始值

    如果枚举的原始值类型是 Int、String,Swift 会自动分配原始值。

    enum Direction : String {
        case north = "north"
        case south = "south"
        case east = "east"
        case west = "west"
    }
    
    // 等价于
    enum Direction : String {
        case north, south, east, west
    }
    
    print(Direction.north) // north
    print(Direction.north.rawValue) // north
    
    enum Season : Int {
        case spring, summer, autumn, winter
    }
    print(Season.spring.rawValue) // 0
    print(Season.summer.rawValue) // 1
    print(Season.autumn.rawValue) // 2
    print(Season.winter.rawValue) // 3
    
    enum Season : Int {
        case spring = 1, summer, autumn = 4, winter
    }
    print(Season.spring.rawValue) // 1
    print(Season.summer.rawValue) // 2
    print(Season.autumn.rawValue) // 4
    print(Season.winter.rawValue) // 5
    
    5、递归枚举
    indirect enum ArithExpr {
        case number(Int)
        case sum(ArithExpr, ArithExpr)
        case difference(ArithExpr, ArithExpr)
    }
    
    //等价于
    enum ArithExpr {
        case number(Int)
        indirect case sum(ArithExpr, ArithExpr)
        indirect case difference(ArithExpr, ArithExpr)
    }
    
    //使用
    let five = ArithExpr.number(5)
    let four = ArithExpr.number(4)
    let two = ArithExpr.number(2)
    let sum = ArithExpr.sum(five, four)
    let difference = ArithExpr.difference(sum, two)
    
    func calculate(_ expr: ArithExpr) -> Int {
        switch expr {
        case let .number(value):
            return value
        case let .sum(left, right):
            return calculate(left) + calculate(right)
        case let .difference(left, right):
            return calculate(left) - calculate(right)
        }
    }
    calculate(difference)
    
    问与答

    1、以下枚举的内存

    enum TestEnum {
        case test
    }
    print(MemoryLayout<TestEnum>.stride)  //1
    print(MemoryLayout<TestEnum>.size)  //0, 
    print(MemoryLayout<TestEnum>.alignment)//1
    
    分析:只有一个 case,无所谓用不用内存
    
    enum TestEnum {
        case test1, test2, test3
    }
    print(MemoryLayout<TestEnum>.stride)  //1
    print(MemoryLayout<TestEnum>.size)  //1
    print(MemoryLayout<TestEnum>.alignment)  //1
    
    分析:用 1 个字节来区分 case
    
    enum TestEnum : Int {
        case test1 = 1, test2 = 2, test3 = 3
    }
    print(MemoryLayout<TestEnum>.stride)  //1
    print(MemoryLayout<TestEnum>.size)  //1
    print(MemoryLayout<TestEnum>.alignment)  //1
    
    分析:初始值不存在枚举内存中,用 1 个字节来区分 case 即可
    
    enum TestEnum {
        case test(Int)
    }
    print(MemoryLayout<TestEnum>.stride)  //8
    print(MemoryLayout<TestEnum>.size)  //8
    print(MemoryLayout<TestEnum>.alignment)  //8
    
    分析:关联值存储在枚举内存中,1 个 case 关联值类型为 int 用 8 个字节
    
    enum TestEnum {
        case test1(Int, Int, Int)
        case test2(Int, Int)
        case test3(Int)
        case test4(Bool)
        case test5
    }
    print(MemoryLayout<TestEnum>.stride)  //32
    print(MemoryLayout<TestEnum>.size)  //25
    print(MemoryLayout<TestEnum>.alignment)  //8
    
    分析:
    1 个字节用来区分 case。
    N 个字节用来存储 case 占用内存最大的关联值。所有 case 的关联值共用这 N 个字节。
    故:该枚举中实际分配 32 字节,占用 25 字节,内存对齐为 8 字节。
    
    enum TestEnum {
        case test0
        case test1
        case test2
        case test4(Int)
        case test5(Int, Int)
        case test6(Int, Int, Int, Bool)
    }
    print(MemoryLayout<TestEnum>.stride)  //32
    print(MemoryLayout<TestEnum>.size)  //25
    print(MemoryLayout<TestEnum>.alignment)  //8
    
    分析:
    
    enum TestEnum {
        case test0
        case test1
        case test2
        case test4(Int)
        case test5(Int, Int)
        case test6(Int, Int, Bool, Int)
    }
    print(MemoryLayout<TestEnum>.stride)  //32
    print(MemoryLayout<TestEnum>.size)  //32
    print(MemoryLayout<TestEnum>.alignment)  //8
    
    分析:
    
    enum TestEnum {
        case test0
        case test1
        case test2
        case test4(Int)
        case test5(Int, Int)
        case test6(Int, Bool, Int)
    }
    print(MemoryLayout<TestEnum>.stride)  //32
    print(MemoryLayout<TestEnum>.size)  //25
    print(MemoryLayout<TestEnum>.alignment)  //8
    
    分析:
    

    2、枚举的关联值和原始值
    定义:
    关联值:将枚举的成员值跟其他类型的值关联存储在一起。关联值是直接存储在枚举类型的内存中。
    原始值:枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做:原始值。

    内存对比:
    关联值:关联值是直接存储在枚举类型的内存中。枚举内存的大小 = N 个字节(枚举关联值类型中最大的 case) + 1 个字节(case 大于 1 个时,该字节用来区分 case,否则不需要该字节)。枚举中任何一个 case 都共用这 N 个字节。
    原始值:原始值的 case 值是固定的,不可以更改。原始值不占用枚举变量的内存,也就是不会将值存储到枚举变量的内存中。

    MemoryLayout

    可以使用 MemoryLayout 获取数据类型占用的内存大小。

    enum Password {
        case number(Int, Int, Int, Int)
        case other
    }
    
    MemoryLayout<Password>.stride // 40, 分配占用的空间大小
    MemoryLayout<Password>.size // 33, 实际用到的空间大小
    MemoryLayout<Password>.alignment // 8, 对齐参数
    
    var pwd = Password.number(9, 8, 6, 4)
    pwd = .other
    MemoryLayout.stride(ofValue: pwd) // 40
    MemoryLayout.size(ofValue: pwd) // 33
    MemoryLayout.alignment(ofValue: pwd) // 8
    

    4、可选类型

    可选类型,它允许将值设置为 nil。在类型名称后面加个问号 ? 来定义一个可选项。Optional 是一个泛型枚举

    enum Optional<Wrapped> {
      case none
      case some(Wrapped)
    }
    

    Optional 类型表示:有值 / 没有值。

    强行打开 - 不安全
    let a: String = x!
    
    隐式解包变量声明 - 在许多情况下不安全
    var a = x!
    
    可选链接 - 安全
    let a = x?.count
    
    无合并操作员 - 安全
    let a = x ?? ""
    
    可选绑定 - 安全
    if let a = x {
      print("x was successfully unwrapped and is = \(a)")
    }
    
    警卫声明 - 安全
    guard let a = x else {
      return
    }
    
    可选模式 - 安全
    if case let a? = x {
      print(a)
    }
    

    5、结构体和类

    1、结构体

    在 Swift 标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分。比如 Bool、Int、Double、 String、Array、Dictionary 等常见类型都是结构体。所有的结构体都有一个编译器自动生成的初始化器。

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

    自定义初始化器:一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器。

    2、类

    类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器。

    类的初始化器:如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器。成员的初始化是在这个初始化器中完成的。

    3、结构体和类的区别

    结构体是值类型(枚举也是值类型),类是引用类型(指针类型)。

    值类型:值类型赋值给 var、let 或者给函数传参,是直接将所有内容拷贝一份。产生了全新的文件副本,属于深拷贝。

    值类型的写时复制
    在 Swift 标准库中,为了提升性能,String、Array、Dictionary、Set 采取了 Copy On Write 的技术。比如仅当有操作时,才会真正执行拷贝操作。对于标准库值类型的赋值操作,Swift 能确保最佳性能,所以没必要为了保证最佳性能来避免赋值。 建议:不需要修改的,尽量定义成 let。

    引用类型:引用赋值给 var、let 或者给函数传参,是将内存地址拷贝一份,属于浅拷贝。

    嵌套类型:

    struct Poker {
        enum Suit : Character {
            case spades = "♠", hearts = "♥", diamonds = "♦", clubs = "♣"
        }
        enum Rank : Int {
            case two = 2, three, four, five, six, seven, eight, nine, ten
            case jack, queen, king, ace
        }
    }
    
    print(Poker.Suit.hearts.rawValue)
    var suit = Poker.Suit.spades
    suit = .diamonds
    var rank = Poker.Rank.five
    rank = .king
    

    枚举、结构体、类都可以定义方法
    一般把定义在枚举、结构体、类内部的函数,叫做方法。

    class Size {
        var width = 10
        var height = 10
        func show() {
            print("width=\(width), height=\(height)")
        }
    }
    let s = Size()
    s.show() // width=10, height=10
    
    enum PokerFace : Character {
        case spades = "♠", hearts = "♥", diamonds = "♦", clubs = "♣"
        func show() {
            print("face is \(rawValue)")
        }
    }
    let pf = PokerFace.hearts
    pf.show() // face is ♥
    
    struct Point {
        var x = 10
        var y = 10
        func show() {
            print("x=\(x), y=\(y)")
        }
    }
    let p = Point()
    p.show() // x=10, y=10
    

    方法占用对象的内存么?
    不占用。方法的本质就是函数,方法、函数都存放在代码段。

    6、闭包

    1、函数表达式

    在 Swift 中,可以通过 func 定义一个函数,也可以通过闭包表达式定义一个函数。

    //函数表达式 
    func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
    
    //闭包表达式定义一个函数
    var fn = {(v1: Int, v2: Int) -> Int in
        return v1 + v2
    }
    fn(10, 20)
    
    //闭包表达式
    var fn = {(参数列表) -> 返回值类型 in
        函数体代码
    }
    fn(X, Y)//函数调用(fn 为函数名)
    
    2、闭包表达式的简写
    //示例
    func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
        print(fn(v1, v2))
    }
    
    //调用简写过程
    exec(v1: 10, v2: 20, fn: {(v1: Int, v2: Int) -> Int in
        return v1 + v2
    })
    exec(v1: 10, v2: 20, fn: {v1, v2 in 
        return v1 + v2
    })
    exec(v1: 10, v2: 20, fn: {v1, v2 in 
        v1 + v2
    })
    exec(v1: 10, v2: 20, fn: { $0 + $1 })
    exec(v1: 10, v2: 20, fn: +)
    
    3、尾随闭包

    如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性。
    1、尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式。
    2、如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号。

    func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
        print(fn(v1, v2))
    }
    
    exec(v1: 10, v2: 20) {$0 + $1}
    
    func exec(fn: (Int, Int) -> Int) {
        print(fn(1, 2))
    }
    
    exec(fn: { $0 + $1 })
    exec() { $0 + $1 }
    exec { $0 + $1 }
    

    示例:数组排序

    func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
    
    /// 返回true: i1排在i2前面
    /// 返回false: i1排在i2后面
    func cmp(i1: Int, i2: Int) -> Bool {
        // 大的排在前面
        return i1 > i2
    }
    
    //使用
    var nums = [11, 2, 18, 6, 5, 68, 45]
    nums.sort(by: cmp)
    // [68, 45, 18, 11, 6, 5, 2]
    
    //闭包方式,以下所有方式等效
    nums.sort(by: {(i1: Int, i2: Int) -> Bool in
        return i1 < i2
    })
    nums.sort(by: { i1, i2 in return i1 < i2 })
    nums.sort(by: { i1, i2 in i1 < i2 })
    nums.sort(by: { $0 < $1 })
    nums.sort(by: <)
    nums.sort() { $0 < $1 }
    nums.sort { $0 < $1 }
    //结果: [2, 5, 6, 11, 18, 45, 68]
    
    4、自动闭包

    示例:如果第1个数大于0,返回第一个数。否则返回第2个数

    func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
        return v1 > 0 ? v1 : v2
    }
    getFirstPositive(10, 20) // 10
    getFirstPositive(-2, 20) // 20
    getFirstPositive(0, -4) // -4
    
    //修改需求:当 v1 > 0 成立的时候返回 v1 并不执行 v2(), 当 v1 > 0 不成立的时候才执行 v2()
    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 会自动将 20 封装成 { 20 }
    @autoclosure 只支持 () -> T 格式的参数
    @autoclosure 并非只可以是最后一个参数
    ?? 运算符其实就是 @autoclosure 
    有 @autoclosure  的函数和无 @autoclosure 的函数会构成函数重载
    
    5、闭包

    一个函数和它所捕获的变量\常量环境组合起来,称为闭包。一般指定义在函数内部的函数。一般它捕获的是外层函数的局部变量\常量。


    可以把闭包想象成是一个类的实例对象,其同样包含类相关信息、引用计数、成员变量。
    1、内存在堆空间
    2、捕获的局部变量\常量就是对象的成员(存储属性)
    3、组成闭包的函数就是类内部定义的方法

    //闭包内容可以类比如下
    class Closure {   //闭包可以比作为类
        var num = 0    //闭包捕获的常量/变量
        func plus(_ i: Int) -> Int {  //闭包方法
            num += i
            return num
        }
    }
    var cs1 = Closure()
    var cs2 = Closure()
    cs1.plus(1) // 1
    cs2.plus(2) // 2
    cs1.plus(3) // 4
    cs2.plus(4) // 6
    cs1.plus(5) // 9
    cs2.plus(6) // 12
    

    相关文章

      网友评论

          本文标题:Swift 之流程控制、函数、枚举、可选类型、结构体和类、闭包

          本文链接:https://www.haomeiwen.com/subject/lqebbdtx.html