美文网首页
swift 函数类型+高阶函数

swift 函数类型+高阶函数

作者: 精神薇 | 来源:发表于2022-07-08 16:21 被阅读0次

    Swift 函数用来完成特定任务的独立的代码块。
    Swift使用一个统一的语法来表示简单的C语言风格的函数到复杂的Objective-C语言风格的方法。
    函数声明: 告诉编译器函数的名字,返回类型及参数。
    函数定义: 提供了函数的实体。

    函数

    Swift 函数包含了参数类型及返回值类型:

    函数定义

    函数的参数传递的顺序必须与参数列表相同。
    函数的实参传递的顺序必须与形参列表相同,-> 后定义函数的返回值类型。

    //语法
    func funcname(形参) -> returntype
    {
       Statement1
       Statement2
       ……
       Statement N
       return parameters
    }
    //例子
    func runoob(site: String) -> String {
        return (site)
    }
    

    函数参数+不带参数函数+没有返回值函数

    函数可以接受一个或者多个参数,这些参数被包含在函数的括号之中,以逗号分隔。

    func runoob(name: String, site: String) -> String {
        return name + site
    }
    func sitename() -> String {
        return "123"
    }
    

    元组作为函数返回值

    函数返回值类型可以是字符串,整型,浮点型等。
    元组与数组类似,不同的是,\color{#FF00FF}{元组中的元素可以是任意类型,使用的是圆括号}
    你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。
    下面的这个例子中,定义了一个名为minMax(_:)的函数,作用是在一个Int数组中找出最小值与最大值。

    func minMax(array: [Int]) -> (min: Int, max: Int) {
        var currentMin = array[0]
        var currentMax = array[0]
        for value in array[1..<array.count] {
            if value < currentMin {
                currentMin = value
            } else if value > currentMax {
                currentMax = value
            }
        }
        return (currentMin, currentMax)
    }
    let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
    print("最小值为 \(bounds.min) ,最大值为 \(bounds.max)")
    //最小值为 -6 ,最大值为 109
    

    如果你不确定返回的元组一定不为nil,那么你可以返回一个可选的元组类型。
    你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如(Int, Int)?或(String, Int, Bool)?

    注意
    可选元组类型如(Int, Int)?与元组包含可选类型如(Int?, Int?)是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。

    func minMax(array: [Int]) -> (min: Int, max: Int)? {
        if array.isEmpty { return nil }
        var currentMin = array[0]
        var currentMax = array[0]
        for value in array[1..<array.count] {
            if value < currentMin {
                currentMin = value
            } else if value > currentMax {
                currentMax = value
            }
        }
        return (currentMin, currentMax)
    }
    if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
        print("最小值为 \(bounds.min),最大值为 \(bounds.max)")
    }
    
    //没有返回值函数
    func runoob(site: String) {
        print(site)
    }
    

    函数参数名称

    函数参数都有一个外部参数名和一个局部参数名。

    局部参数名

    局部参数名在函数的实现内部使用。

    //以下实例中 number 为局部参数名,只能在函数体内使用。
    func sample(number: Int) {
       println(number)
    }
    
    外部参数名

    你可以在局部参数名前指定外部参数名,中间以空格分隔,外部参数名用于在函数调用时传递给函数的参数。
    如下你可以定义以下两个函数参数名并调用它:

    func pow(firstArg a: Int, secondArg b: Int) -> Int {
       var res = a
       for _ in 1..<b {
          res = res * a
       }
       print(res)
       return res
    }
    pow(firstArg:5, secondArg:3)
    //125
    

    注意
    如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。

    可变参数

    可变参数可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数,其数量是不确定的。
    可变参数通过在变量类型名后面加入(...)的方式来定义。

    func vari<N>(members: N...){
        for i in members {
            print(i)
        }
    }
    vari(members: 4,3,5)
    vari(members: 4.5, 3.1, 5.6)
    vari(members: "Google", "Baidu", "Runoob")
    

    常量,变量及 I/O 参数

    一般默认在函数中定义的参数都是常量参数,也就是这个参数你只可以查询使用,不能改变它的值。
    如果想要声明一个变量参数,可以在参数定义前加\color{#FF00FF}{inout} 关键字,这样就可以改变这个参数的值了。

    func  getName(_ name: inout String).........
    

    此时这个 name 值可以在函数中改变。
    一般默认的参数传递都是传值调用的,而不是传引用。所以传入的参数在函数内改变,并不影响原来的那个参数。传入的只是这个参数的副本。
    当传入的参数作为输入输出参数时,需要在参数名前加 & 符,表示这个值可以被函数修改。 以下是实例:

    func swapTwoInts(_ a: inout Int, _ b: inout Int) {
        let temporaryA = a
        a = b
        b = temporaryA
    }
    
    
    var x = 1
    var y = 5
    swapTwoInts(&x, &y)
    print("x 现在的值 \(x), y 现在的值 \(y)")
    //x 现在的值 5, y 现在的值 1
    

    函数使用

    在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:

    func sum(a: Int, b: Int) -> Int {
       return a + b
    }
    var addition: (Int, Int) -> Int = sum
    print("输出结果: \(addition(40, 89))")
    //输出结果: 129
    

    解析:
    "定义一个叫做 addition 的变量,参数与返回值类型均是 Int ,并让这个新变量指向 sum 函数"。
    sum 和 addition 有同样的类型,所以以上操作是合法的。
    现在,你可以用 addition 来调用被赋值的函数了:

    函数类型作为参数类型、函数类型作为返回类型

    func sum(a: Int, b: Int) -> Int {
        return a + b
    }
    func another(addition: (Int, Int) -> Int, a: Int, b: Int) {
        print("输出结果: \(addition(a, b))")
    }
    another(addition: sum, a: 10, b: 20)
    //输出结果: 30
    

    函数嵌套

    函数嵌套指的是函数内定义一个新的函数,外部的函数可以调用函数内定义的函数。

    func calcDecrement(forDecrement total: Int) -> () -> Int {
       var overallDecrement = 0
       func decrementer() -> Int {
          overallDecrement -= total
          return overallDecrement
       }
       return decrementer
    }
    let decrem = calcDecrement(forDecrement: 30)
    print(decrem())
    //-30
    

    高阶函数

    1.map
    对于原始集合里的每一个元素,以一个变换后的元素替换之形成一个新的集合

    let numbers = [1, 2, 4, 5, 10] 
    print(numbers.map{ $0 * 10 })  
    //打印: [10, 20, 40, 50, 100]
    print(numbers.map({ (value) -> String in
        return String(value)
    }))
    // 变成字符串数组
    // Swift中闭包是函数的唯一参数或是其最后一个参数时,map的()可以被省略
    numbers.map { (value) -> String in
        return String(value)
    }
    numbers.map { value in String(value)}
    print(numbers.map{ String($0)})   
    //直接通过$0,$1,$2来顺序调用闭包的参数
    //以上四种方法都是等价的
    

    还有一种应用场景,就是解析可选类型的时候,map和flatMap函数会让你的代码更加优雅。

    举个例子,当解析并判断可选类型的时候,你可能会经过一堆if或者guard判断,如下所示:

    func loadURL(url: URL) {
        print(url.absoluteString)
    }
    
    let urlStr: String? = "https://github.com/wangyanchang21"
    guard let siteStr = urlStr else {
        assert(false)
    }
    guard let url = URL(string: siteStr) else {
        assert(false)
    }
    loadURL(url: url)
    

    如果使用map和flatMap函数的话,就会有十分优雅的感觉。

    // 这行优雅的代码代替上面的代码
    urlStr.flatMap(URL.init).map(loadURL)
    

    但有一点需要注意,这里 map替换 flatMap会报错, 原因在于 flatMap闭包可以返回 nil, 而 map闭包不可以。就如下面的代码编译不会通过:

    // compile error
    // urlStr.map(URL.init).map(loadURL)
    

    2.flatMap
    对于元素是集合的集合,可以得到单级的集合

    let arrayNumbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9 ]]
    print(arrayNumbers.flatMap{ $0 }) 
    //打印: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(arrayNumbers.flatMap{ $0.map{ $0 * 10} })  
    //打印: [10, 20, 30, 40, 50, 60, 70, 80, 90]
    

    但是不是所有的首层元素都可以降维得到单级集合
    1.第一种情况,解析首层元素,若有nil则过滤,就不会降维

    let optLatticeNumbers = [[1, Optional(2), 3], [3, nil, 5], nil]
    // 解析首层元素, 若有nil则过滤, 就不会降维
    let flatMapArr2 = optLatticeNumbers.flatMap { $0 }
    // [[1, 2, 3], [3, nil, 5]]
    

    2.解析首层元素,若没有nil,则会降维

    let latticeNumbers = [[1, Optional(2), 3], [3, nil, 5]]
    // 解析首层元素, 若没有nil, 则会降维
    let flatMapArr = latticeNumbers.flatMap { $0 }
    // [1, 2, 3, 3, nil, 5]
    

    为了将过滤nil和降维两个功能于区分开,swift4.1开始,就只保留了降维的flatMap函数,并弃用了过滤nil的flatMap函数,又用开放的新函数compactMap来替代弃用的函数。
    所以,当需要过滤nil的时候,请使用compactMap函数;当需要进行降维时,请使用flatMap函数。这也就是flatMap和compactMap之间的区别。

    3.compactMap
    过滤空值

    let names: [String?] = ["zhangsan", nil, "lisi", "wangwu", nil,  "zhaoliu"]
    print(names.count)    
    // 6
    print(names.map{ $0?.count })
    //[Optional(8), nil, Optional(4), Optional(6), nil, Optional(7)]
    print(names.compactMap{ $0 })   
    //打印: ["zhangsan", "lisi", "wangwu", "zhaoliu"],过滤了空值
    print(names.compactMap{ $0?.count })   
    //打印不是空值的字符串的个数
    // [8, 4, 6, 7]
    

    注意⚠️:可选类型的 map作用是对可选类型进行解包操作,若有值则进入闭包,并返回一个 Optional类型;若为nil,则直接返回当前可选类型的nil。

    4.filter
    对于原始集合里的每一个元素,通过判定来将其丢弃或者放进新集合

    let numbers = [1, 2, 4, 5, 10]
    print(numbers.filter{$0 > 4})
    //打印: [5, 10]
    

    5.reduce
    对于原始集合里的每一个元素,作用于当前累积的结果上

    let numbers = [1, 2, 4, 5, 10]
    print(numbers.reduce(100, { $0 + $1 }))   
    //打印: 122
    // 100 是初始值, 将所有数相加: 100 + 1 + 2 + 4 + 5 + 10 = 122
    

    6.sort函数
    对原集合进行给定条件排序。
    无返回值,直接修改原集合,所以这个集合应该是可变类型的。

    var numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    numbers.sort { a, b in
        return a < b
    }       
    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    

    另外,系统还定义了一个sort()函数,即对集合进行升序排序的函数。但这个函数并不是上面函数不传入缺省值的情况,而是另外一个函数。

    var numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    numbers.sort()      
    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    

    7.sorted函数
    sorted函数与sort函数对应。
    将集合进行给定条件排序,返回一个新的集合,不修改原集合。

    let sortedArr = numbers.sorted { a, b in
        return a > b
    }
    // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    
    // sorted()函数
    let sortedArr2 = numbers.sorted()
    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    // 闭包简写
    let sortedArr3 = sortedArr2.sorted(by: >)
    // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    

    8.prefix函数
    正向取满足条件的元素,进行新集合创建。一旦出现不满足条件的元素,则跳出循环,不再执行。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let prefixArr = numbers.prefix { $0 < 10 }
    // [7, 6]
    

    prefix相关函数:
    \color{#FF00FF}{upTo}: 正向取元素创建数组, 包含小于指定index的元素

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let prefixUpToArr = numbers.prefix(upTo: 5)
    // [7, 6, 10, 9, 8]
    

    \color{#FF00FF}{through}:正向取元素创建数组, 包含小于等于指定index的元素

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let prefixThroughArr = numbers.prefix(through: 2)
    // [7, 6, 10]
    

    \color{#FF00FF}{maxLength}: 正向取元素创建数组, 包含指定的元素个数

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let prefixMaxLengthArr = numbers.prefix(6)
    // [7, 6, 10, 9, 8, 1]
    

    9.drop函数
    与prefix函数对应。正向跳过满足条件的元素,进行新集合创建。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let dropArr = numbers.drop { $0 < 10 }
    // [10, 9, 8, 1, 2, 3, 4, 5]
    

    drop相关函数:
    \color{#FF00FF}{dropFirst}: 正向跳过元素创建数组, 跳过指定元素个数, 缺省值为1

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let dropFirstArr = numbers.dropFirst(3)
    // [9, 8, 1, 2, 3, 4, 5]
    

    \color{#FF00FF}{dropLast}:返向跳过元素创建数组, 跳过指定元素个数, 缺省值为1

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let dropLastArr = numbers.dropLast(5)
    // [7, 6, 10, 9, 8]
    

    10.first函数
    正向找出第一个满足条件的元素。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let first = numbers.first { $0 < 7 }
    // 6
    

    11.last函数
    与first函数对应。反向找出第一个满足条件的元素。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let last = numbers.last { $0 > 5 }
    // 8
    

    12.firstIndex函数
    正向找出第一个满足条件的元素下标。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let firstIndex = numbers.firstIndex { $0 < 7 }
    // 1
    

    13.lastIndex函数
    反向找出第一个满足条件的元素下标。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let lastIndex = numbers.lastIndex { $0 > 5 }
    // 4
    

    14.partition函数()(不常用)
    按照条件进行重新排序,不满足条件的元素在集合前半部分,满足条件的元素后半部分,但不是完整的升序或者降序排列。
    返回值为排序完成后集合中第一个满足条件的元素下标。

    var partitionNumbers = [20, 50, 30, 10, 40, 20, 60]
    let pIndex = partitionNumbers.partition { $0 > 30 }
    // partitionNumbers = [20, 20, 30, 10, 40, 50, 60]
    // pIndex = 4
    

    15.min函数
    按条件排序后取最小元素。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let min = numbers.min { $0 % 5 < $1 % 5 }
    // 10
    

    min()函数,自然升序取最小。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let minDefault = numbers.min()
    // 1
    

    16.max函数
    按条件排序后取最大元素。

    let maxDictionary = ["aKey": 33, "bKey": 66, "cKey": 99]
    let max = maxDictionary.max { $0.value < $1.value }
    // (key "cKey", value 99)
    

    max()函数,自然升序取最大。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    let maxDefault = numbers.max()
    // 10
    

    17.removeAll函数
    移除原集合中所有满足条件的元素。
    无返回值,直接修改原集合,所以这个集合应该是可变类型的。

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    var removeArr = numbers
    removeArr.removeAll { $0 > 6 }
    // [6, 1, 2, 3, 4, 5]
    

    18.集合遍历

    let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
    //forEach函数:
    numbers.forEach { num in
        print(num)
    }
    //for-in函数:
    for num in numbers where num < 5 {
        print(num)
    }
    //与enumerated()函数配合使用:
    for (index, num) in numbers.enumerated() {
        print("\(index)-\(num)")
    }
    

    19.shuffled函数
    shuffled函数,打乱集合中元素的的顺序。

    let ascendingNumbers = 0...9
    let shuffledArr = ascendingNumbers.shuffled()
    // [3, 9, 2, 6, 4, 5, 0, 1, 7, 8]
    

    20.split和joined函数
    split函数,字符串的函数,按条件分割字符串,为子字符串创建集合。与Objective-C中的componentsSeparatedByString:方法类似。

    let line = "123Hi!123I'm123a123coder.123"
    let splitArr = line.split { $0.isNumber }
    // ["Hi!", "I'm", "a", "coder."]
    
    // 也可指定字符
    let splitArr2 = line.split(separator: "1")
    // ["23Hi!", "23I'm", "23a", "23coder.", "23"]
    

    joined函数,数组元素连接指定字符拼接成一个字符串。与Objective-C中的componentsJoinedByString:方法类似。

    let joined = splitArr.joined(separator: "_")
    // "Hi!_I'm_a_coder."
    // 也可以只传入字符
    let joined2 = splitArr2.joined(separator: "#")
    // "23Hi!#23I'm#23a#23coder.#23"
    

    21.zip函数
    将两个数组合并为一个元组组成的数组。

    let titles = ["aaa", "bbb", "ccc"]
    let numbers = [111, 222, 333]
    let zipA = zip(titles, numbers)
    for (title, num) in zipA {
        print("\(title)-\(num)")
    }
    //aaa-111
    //bbb-222
    //ccc-333
    

    内联函数(Inline Function)

    如果开启了编译器优化,编译器会自动将某些函数变成内联函数(将函数调用展开成函数体),我们可以看到 Release 模式默认开启优化,并且是按照速度去优化:



    比如我们有一个函数,那么一旦调用 test() 这个函数,系统就会为这个函数分配栈空间,并且在栈空间进行分配局部变量的操作,函数执行完之后会对函数栈空间进行回收:

    func test() {
        print("test")
    }
    test()
    

    我们思考一下,test() 这个函数里面的代码非常少,仅仅做一件事情打印,那么我们直接把 test() 函数里这行代码拿出来,这样性能不就更好吗?其实内联做的操作就是这样,将函数调用展开成函数体代码,这样就减少了函数的调用开销,不必再开辟回收函数的栈空间了。展开后代码如下:

    print("test")
    

    接下来我们运行看一下汇编代码,首先在第76行代码打一个断点,然后打开显示反汇编选项(Debug -> Debug Workflow -> Always show Disassembly):



    1、首先编译器未开启优化的情况:


     0x10b294a65 <+5989>: callq  0x10b2956b0               ; test() -> () at ViewController.swift:73
    

    我们发现有一个 callq 0x10b2956b0 的操作,这句汇编代码的意思就是调用 test() 函数,所以未开启优化的情况下没有进行内联操作。

    2、然后我们打开编译器优化选项:



    我们在 test() 函数调用的地方第76行设置断点,然后运行,我们发现 test() 函数没有执行,但是 “test” 字符串被打印出来了:


    然后我们把断点设置到第 74 行,运行查看汇编代码


    0x10ae951cf <+1871>: callq  0x10ae96e1a               ; symbol stub for: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ()
    

    我们看汇编可以发现,print(“test”)代码被直接放到 main 函数当中,也就是编译器帮我们做了内联操作。

    但是有一些函数是不会被内联:
    1、函数体比较长的函数不会被内联(如果函数体比较长,函数被调用次数也非常多,进行内联操作生成的汇编代码会非常多,也就是机器码会变多,最终代码体积变大安装包变大);
    2、递归调用不会被内联;
    3、包含动态派发(类似Oc动态绑定)的函数不会被内联;
    我们也可以使用@online手动关闭/开启内联优化的:
    1、下面的函数永远不会被内联(即使开启了编译器优化):

    @inline(never) func test() {
        print("test")
    }
    

    2、开启编译器优化后,即使代码很长也会被内联(递归调用,动态派发的函数除外;在 release 模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用 @inline):

    @inline(__always) func test() {
        print("test")
    }
    

    相关文章

      网友评论

          本文标题:swift 函数类型+高阶函数

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