• 苹果新的编程语言 Swift 语言进阶(六)--函数和闭包


    一 、函数

            1.1、 函数的定义和调用

            函数的定义以func关键字作为前缀,接着是函数名字,接着跟着一个可以带有参数,也可以不带参数的圆括号,接着用-> 指示函数的返回类型。函数执行体用一对大括号{}包围。如下定义了一个函数名为sayHello的函数,该函数包含一个名字为personName,类型为String的输入参数。

    func sayHello(personName:String) -> String {

       let greeting ="Hello, " +personName +"!"

       return greeting

    }

       你能使用上面定义的函数名sayHello并在圆括号中包含一个传给该函数的一个字符串参数值来调用该函数。例如   

       sayHello(“Anna”)。


       Swift也可以定义一个不带返回值的函数,如

    func sayGoodbye(personName:String) {

       println("Goodbye,(personName)!")

    }

       注: 没有定义返回值函数的返回类型为Void,是一个空的多元组(包含零个元素),写作()。

       Swift中可以使用一个多元组类型作为函数的返回类型,以便从一个函数返回多个值。

    func count(string:String) -> (vowels:Int,consonants:Int,others:Int) {

        return (vowels,consonants,others)

    }

       以上定义的函数中的参数在圆括号中以parameterName:parameterType形式定义,以这种形式定义的参数称为本地参数,即只能在函数执行体内部使用,不能在函数调用时使用。

       Swift支持为一个函数的每一个参数命名一个外部参数,外部参数在函数本地参数的前面声明。如下所示:

    func join(string s1:String,toString s2:String,withJoiner joiner:String)

        ->String {

           return s1 +joiner +s2

    }

       为一个函数命名的外部参数可以在也只能在函数调用时使用。如下所示在调用上面的函数时加上以上为参数命名的外部参数名字。

         join(string:"hello",toString:"world",withJoiner:", “)

       为函数参数命名一个外部参数主要是为了使函数的参数意义更加清晰。

       如果一个函数的本地参数名字已经比较适当,这时可以在函数的本地参数名字前加上一个’#’符号,指示该参数名字即用作本地参数名,又用于外部参数名。如:

    func containsCharacter(#string:String, #characterToFind:Character) ->Bool {

     

    }

       Swift中,你还能在函数定义中为函数的任意参数定义一个默认值。如果为一个函数的某个参数提供了默认值,在调用该函数时就能够不传送定义默认值的参数。

       定义默认值的参数要放到函数参数列表的末端,没有定义默认值的参数要放到函数参数列表的前面,以便调用函数的形式统一。


    fund join(string s1:String,toString s2:String,

       withJoiner joiner:String =" ") ->String {

           return s1 +joiner +s2

    }

       以上join函数为joiner参数定义了默认参数,因此可以用如下方式调用该函数,第三个参数没有被传递,函数内部使用其默认值:

         join(string:"hello",toString:"world")

         // returns "hello world”

       多数情况下为带默认值的参数提供一个外部名字是必要和有用的,这样能够在调用该函数时使函数表达的功能更加清晰。

       因此针对这种情况,如果不为默认参数提供一个外部名字,Swift自动为其分配一个外部参数名,自动分配的外部参数名与其本地名字相同。如下例子:

    func join(s1:String,s2:String,joiner:String =" ") ->String {

       return s1 +joiner +s2

    }

       如上函数Swift自动为带默认值的joiner参数提供了一个外部参数名,其名字和其本地名字joiner相同。

       因此调用该函数时必须为其提供外部名字,从而使函数参数的表达的意思更加清楚和没有歧义。

              join("hello","world",joiner:“-")

     Swift 也支持函数带有可变参数,用来接受零个或多个特定类型的参数。可变参数在参数类型后面加...符号标识。传给函数的可变参数在函数体内作为一个适当类型的数组使用。

         如下例子展示了一个使用可变参数的函数例子。

    func arithmeticMean(numbers:Double...) -> Double {

       var total:Double =0

       for number in numbers {

           total +=number

        }

       return total /Double(numbers.count)

    }

        为了避免歧义,函数的可变参数总是出现在函数参数列表的最后。

        函数参数默认是常量类型,不需要加let 标识。但 Swift 支持在函数定义中使用var关键字定义变量参数,变量参数也只在函数执行体内部有效和使用。

    func alignRight(var string:String,count:Int,pad:Character) ->String {

       let amountToPad =count -countElements(string)

       for in ... amountToPad {

           string =pad +string

        }

       return string

    }

       Swift 为了支持一个函数体内部对参数修改的值仍能够在函数调用结束后被外部所用,定义了一种in-out 参数。

       Swift在参数定义的前面添加一个inout关键字来定义一个in-out 参数。in-out 参数可以在函数内部修改传进来的值,并传回代替原先的值。由于in-out 参数作为一个变量使用,因此在调用函数时需要在其名字前面需要放一个&标记符来指示该参数是一个函数内部可以修改并传回值的in-out 参数。

       需要注意的是in-out 参数不能没有默认值,可变参数也不能标记为inout参数,也不能使用var或let关键字来标记它。

    func swapTwoInts(inout a:Int,inout b:Int) {

       let temporaryA =a

       a =b

       b =temporaryA

    }

           以上swapTwoInts函数定义了两个inout参数,用来在函数内部实现两个参数值的交换。可以使用如下方式调用该函数:

    var someInt =3

    var anotherInt =107

    swapTwoInts(&someInt, &anotherInt)

    1.2、 函数类型及使用

        每个函数都属于一种特定的函数类型,函数类型由参数类型和函数的返回类型组成。

    func addTwoInts(a:Int,b:Int) ->Int {

       return a +b

    }

    func multiplyTwoInts(a:Int,b:Int) ->Int {

       return a *b

    }

        例如以上定义的两个函数属于相同的函数类型,其类型为:(Int, Int) -> Int

            在Swift中可以像其它类型一样使用函数类型(函数类型是一种引用类型),如定义一个常量或变量是一个函数类型并为其分配一个适当的函数。

        let anotherMathFunction =addTwoInts

        // anotherMathFunction 被推断为是一个(Int, Int) -> Int函数类型。


             你也能够使用函数类型作为另外函数的参数。如下所示:

    func printMathResult(mathFunction: (Int,Int) ->Int, a:Int, b:Int) {

       println("Result:(mathFunction(a,b))")

    }

          printMathResult(addTwoInts,3,5)

        也能够使用函数类型作为另外函数的返回类型,如:

    func stepForward(input:Int) ->Int {

       return input +1

    }

    func stepBackward(input:Int) ->Int {

       return input -1

    }

          以上定义了具有相同类型的两个函数。

    func chooseStepFunction(backwards:Bool) -> (Int) ->Int {

       return backwards ?stepBackward :stepForward

    }

          // 该函数根据参数的不同值返回不同的函数。 

         var currentValue =3

    let moveNearerToZero =chooseStepFunction(currentValue >0)

           currentValue =moveNearerToZero(currentValue)   


      1.3、函数的嵌套

          在Swift中,可以在函数执行体内定义其它函数,被称为函数嵌套,一个函数内部定义的函数称为嵌套函数,最外面定义的函数统称为全局函数。嵌套函数默认对外部隐藏。仅能在定义它的函数内部调用或使用。但包含一个嵌套函数的函数可以返回它所包含的嵌套函数以便嵌套函数能够被外部使用。如下所示:

    func chooseStepFunction(backwards:Bool) -> (Int) ->Int {

       func stepForward(input:Int) ->Int {return input +1 }

       func stepBackward(input:Int) ->Int {return input -1 }

       return backwards ?stepBackward :stepForward

    }

     以上函数chooseStepFunction内部定义了两个内嵌函数,并根据传进的参数值返回两个内嵌函数之一。

    二 闭包(Closures)

       2.1 闭包定义

         闭包是一个自包含的功能块,能像函数一样使用,闭包类似于C 和 Objective-C语言中定义的块(blocks)。

         像函数一样闭包也是一种类型(引用类型),也能分配一个闭包到一个常量或变量,实际上该常量或变量指向该闭包的一个引用。


         全局函数和嵌套函数是闭包的特例。Swift中闭包指的是如下三种形式之一:

         1) 全局函数,全局函数不能够捕获任何值;

         2) 嵌套函数,嵌套函数能从定义它们的函数中捕获值;

         3) 闭包表达式是没有名字的闭包,是实现闭包的轻量级语法形式,闭包表达式能从它们的使用上下文中捕获值。

         闭包表达式的语法形式为:

    { (parameters) -> return type in

        statements

    }

         闭包表达式与函数的主要不同是闭包表达式没有函数名。整个闭包表达式的内容用一对大括号包括,闭包表达式的参数和返回类型也在大括号内部声明,并in关键字来引出闭包表达式的执行体。

         闭包表达式像函数一样也能使用常量参数、变量参数或inout参数,也可以使用可变参数,但不同的地方是不能给闭包表达式的参数提供默认值。

         如下是inline方式使用闭包表达式的一个例子。

         reversed = sort(names, { (s1:String, s2:String) -> Bool in return s1 > s2 } )

    2.2  闭包表达式的优化

         当以inline闭包表达式传送一个闭包给一个函数时,闭包表达式的参数和类型可以从函数的参数中加以推断,如

         上面的sorting闭包作为sort函数的第二个参数传递和使用,Swift能从sort函数的第二个参数的类型来推断该闭包的的参数类型和返回类型为(String, String) -> Bool类型。

         因此对于这种情况,闭包表达式的参数和返回类型在实际代码中很少需要声明,可以简化为如下形式:

          reversed =sort(names, {s1,s2 in return s1 >s2 } )

         Swift中,单表达式的闭包能够隐含返回该表达式的运算结果,因此上面的return关键字也能够省略。

         reversed = sort(names, { s1, s2 in s1 > s2 } )

         Swift可以自动为inline闭包表达式的参数提供速记名,使用$0,$1,$2等形式来定义和引用闭包表达式包含的参数值。这时inline闭包表达式的参数列表也能够取消,这时闭包表达式就只包含一个执行体,因此in关键字也可以省掉。  

             这样上面使用inline闭包表达式的sort函数可以被优化为如下最简化形式:

                 reversed =sort(names, {$0 >$1 } )

          由于字符串类型实现了一个 >操作符(大于)的字符串操作函数。该操作函数的函数类型与sort函数的第二个参数需要的函数类型相匹配,因此你能在上面的函数中简单传送一个>操作符,Swift将会帮你推断你想使用它的字符串实现。因此上面的表达式还可以写作如下形式:

           reversed =sort(names, >) 

        如果传送一个闭包表达式作为一个函数的最后一个参数,并且闭包表达式太长。这时还可以使用闭包推后形式。

             闭包推后指的是闭包表达式在调用函数时被写在它支持的函数的参数括号外面。

        因此如上的对sort函数调用时可以写成下面的形式,这样使代码的可读性更强。

        reversed = sort(names) { $0 > $1 }

        如果一个闭包表达式是一个函数的唯一参数,并以闭包推后的形式表达,这时调用函数时函数包含的参数()可以被省去。如下例子所示:

    let strings =numbers.map {

        (var number) ->String in

       var output =""

       while number >0 {

           output =digitNames[number %10]! +output

           number /=10

        }

       return output

    }

    2.3 值的捕获

            闭包能从定义它们的上下文中捕获引用并赋值给任意的常量或变量。

            闭包可以从它被定义的上下文捕获常量或变量,然后在它的执行体内引用或修改捕获的常量或变量,即使定义常量或变量已经不再有效。

            如嵌套函数能从定义它的外部函数的参数和外部函数中捕获参数、任意常量或变量等。

    func makeIncrementor(forIncrement amount:Int) -> () ->Int {

       var runningTotal =0

       func incrementor() ->Int {

           runningTotal +=amount

           return runningTotal

        }

       return incrementor

    }

           以上例子,makeIncrementor的内嵌函数incrementor从它的定义函数makeIncrementor中捕获了一个变量和一个参数。由于捕获的参数在内嵌函数执行体内不被修改,因此以原先值的拷贝形式捕获。而捕获的变量需要在内嵌函数执行体内修改,因此这时内嵌函数捕获的是原先变量的一个引用,捕获一个引用确保在定义内嵌函数的外部函数调用结束后,引用仍能够继续有效,而这些都是由Swift自动处理的,用户不需要任何操作

        如下例子所示每次对内嵌函数的调用,引用值在原先的基础上修改,虽然外部函数makeIncrementor调用已经结束。

         let incrementByTen = makeIncrementor(forIncrement:10)

    incrementByTen()

    // returns a value of 10

    incrementByTen()

    // returns a value of 20

    incrementByTen()

    // returns a value of 30


                             版权所有,转载时请清楚注明链接和出处,谢谢!


  • 相关阅读:
    深度剖析Byteart Retail案例
    REVIT使用中遇到的各种问题汇总
    常用设计规范
    编程修养
    Linux、Windows静态编译ffmpeg 4.4.1、x264、x265等编解码库的脚本
    程序员的灯下黑:重知识轻技术
    GitHub 公布 2021 Top 10 博文「GitHub 热点速览」
    狠人!标星 3.4 万的项目说删就删,几行代码搞崩数万个开源项目
    重装系统前备份fstab
    Ubuntu系统下制作U盘启动盘
  • 原文地址:https://www.cnblogs.com/aiwz/p/6154484.html
Copyright © 2020-2023  润新知