函数

函数

函数定义

当你定义一个函数时,你可以选择性地定义一个或多个名称,类型值作为函数的输入(称为形参),或者定义一个函数结束后返回值的类型(称之为返回型)。每一个函数都有一个函数名,用来描述了函数执行的任务。要使用一个函数时,可使用它的名称进行“调用”,并通过它的输入值(称为实参–argument)来匹配函数的参数类型。一个函数的实参(arguments)必须始终和函数形参(parameter)顺序一致

func sayHello(personName: String) -> String {
  let greeting = "Hello, " + personName + "!"
  return greeting
}

println(sayHello("Anna"))
// prints "Hello, Anna!"
println(sayHello("Brian"))
// prints "Hello, Brian!"

函数返回值

由于 Swift 中存在元组类型(tuples),所以 Swift 允许有多个返回值或者直接无返回值。

无返回值函数

函数不需要定义一个返回类型。这里有一个版本的 sayHello 函数,称为 waveGoodbye,它会打印自己的 String 值而不是返回它:

func sayGoodbye(personName: String) {
println("Goodbye, \(personName)!")
}
sayGoodbye("Dave") // prints "Goodbye, Dave!"

严格地说,sayGoodbye 函数确实还返回一个值,即使没有定义返回值。没有定义返回类型的函数返回了一个 Void 类型的特殊值。这仅是一个空元组,这里边没有元素,可以被写成()。

单返回值函数

两个参数一个返回值,都为 Int

func thirdFunction(firstValue: Int, secondValue: Int) -> Int {
         return firstValue + secondValue
     }
thirdFunction(1, 2)

多返回值函数

可以使用一个元组类型作为函数的返回类型,来返回一个由多个值组成的复合返回值。下面的例子定义了一个名为 count 函数,用来计算字符串中基于标准美式英语的元音、辅音以及字符的数量:

func count(string: String) -> (vowels: Int, consonants: Int, others: Int) {
        var vowels = 0, consonants = 0, others = 0
        for character in string {
            switch String(character).lowercaseString {
            case "a", "e", "i", "o", "u":
                ++vowels
            case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
            "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
            ++consonants
            default:
            ++others
            }
        }
        return (vowels, consonants, others)
    }

上文中关于元组部分讲解时也提及,可以直接利用下标或者属性名来访问元组中值,因此,多返回值也可以这么使用:

func fourthFunction(firstValue: Int, secondValue: Int)
         -> (doubled: Int, quadrupled: Int) {
         return (firstValue * 2, secondValue * 4)
     }
fourthFunction(2, 4)

// 用数字访问:
fourthFunction(2, 4).1 // = 16

// 其他相同,只是使用了名字:
fourthFunction(2, 4).quadrupled // = 16

函数变量

var numbersFunc: (Int, Int) -> Int;
// numbersFunc现在可以存储任何接受两个Int并返回一个Int的函数

numbersFunc = addNumbers
numbersFunc(2, 3) // = 5

参数调用

本地形参与外部形参

注意,全局函数中不可以使用外部形参

所有上面的函数都为其形参定义了形参名:

    func someFunction(parameterName: Int) {
        // function body goes here, and can use parameterName
        // to refer to the argument value for that parameter
    }

然而,这些参数名的仅能在函数本身的主体内使用,不能在调用函数时使用。这种形参类型名称被称之为本地形参名(local parameter name),因为它们只能在函数的主体中使用。有时当你调用一个函数将每个形参进行命名是非常有用的,以表明你把每个实参传递给函数的目的。如果你希望使用你函数的人在调用函数时提供形参名称,那除了本地形参名外,你还要为每个形参定义一个外部形参名称。你写一个外部形参名称在它所支持的本地形参名称之前,之间用一个空格来分隔:

func someFunction(externalParameterName localParameterName: Int) {
        // function body goes here, and can use localParameterName
        // to refer to the argument value for that parameter
    }

    func join(string s1: String, toString s2: String, withJoiner joiner: String)
        -> String {
        return s1 + joiner + s2
    }
    join(string: "hello", toString: "world", withJoiner: ", ")
    // returns "hello, world"

外部参数名称速记#:2.0 之后已经废弃

在 Swift 2.0 中已经不需要使用#来进行外部参数名称速记

Swift gives the first parameter name in a method a local parameter name by default, and gives the second and subsequent parameter names both local and external parameter names by default.

如果你想为一个函数提供一个外部形参名,然而本地形参名已经使用了一个合适的名称了,那你就不需要两次书写该形参的名称。相反,你可以写一次名字,并用一个 hash 符号(#)作为名称的前缀。这就告诉 Swift 使用名称相同的本地行参名称和外部形参名称。这个例子定义了一个名为 containsCharacter 的函数,通过在本地形参名前添加 hash 符号(#)来定义外部形参名称。

func containsCharacter(#string: String, #characterToFind: Character) -> Bool {
    for character in string {
        if character == characterToFind {
            return true
        }
    }
    return false
}

该函数对形参名的选择使得其函数主题更加清晰易读,并且在调用该函数时也不会有歧义:

let containsAVee = containsCharacter(string: "aardvark", characterToFind: "v")
// containsAVee equals true, because "aardvark" contains a "v"

使用_来避免使用下标

func foo(s1: String, _ s2: String) -> String{
    return s1 + s2;
}

在正常的调用 foo 函数时,第二个及以后参数时要使用外部参数名。而使用_描述之后可以避免使用外部参数名:

bar.foo("Hello", "World")

不过需要注意的是,在 init 函数中,从第一个参数开始,就需要使用默认的外部参数名,即:

//不使用_
class IntClass {
    var value: Int
    init(val: Int) { self.value = val }
}
var x = IntClass(val:1)
//使用_
class IntClass {
    var value: Int
    init(_ val: Int) { self.value = val }
}
var x = IntClass(1)

默认形参值

你可以为任何形参定义默认值以作为函数定义的一部分。如果已经定义了默认值,那么调用函数时就可以省略该行参。

注意:请在函数形参列表的末尾放置带默认值的形参。这将确保所有函数调用都使用顺序相同的无默认值实参,并让在每种情况下清晰地调用相同的函数。

这里有一个早期的join函数,并为参数joiner设置了默认值:
func join(string s1: String, toString s2: String,
    withJoiner joiner: String = " ") -> String {
        return s1 + joiner + s2
}
如果在join函数调用时为joiner提供了字符串值,那么该字符串值可以用来连接两个字符串,就跟以前一样:
    join(string: "hello", toString: "world", withJoiner: "-")
    // returns "hello-world"
但是,如果函数调用时没有为joiner提供值,就会使用单个空格(" ")的默认值:
    join(string: "hello", toString: "world")
    // returns "hello world"

不定形参

一个可变形参可接受零个或多个指定类型的值。当函数被调用时,你可以使用可变形参来指定--形参可以用来传递任意数量的输入值。可通过在形参的类型名后边插入三个点符号(...)来编写可变形参。传递至可变形参的值在函数主体内是以适当类型的数组存在的。例如,一个可变参数的名称为numbers和类型为Double...在函数体内就作为名为numbers类型为Double[]的常量数组。

注意:函数最多可以有一个可变形参,而且它必须出现在参数列表的最后,以避免使用多个形参调用函数引发歧义。如果你的函数有一个或多个带有默认值的形参,并且还有可变形参,请将可变形参放在所有默认形参之后,也就是的列表的最末尾。

    func arithmeticMean(numbers: Double...) -> Double {
        var total: Double = 0
        for number in numbers {
            total += number
        }
        return total / Double(numbers.count)
    }
    arithmeticMean(1, 2, 3, 4, 5)
    // returns 3.0, which is the arithmetic mean of these five numbers
    arithmeticMean(3, 8, 19)
    // returns 10.0, which is the arithmetic mean of these three numbers

常量形参和变量形参

**函数的形参默认是常量**。试图在函数体内改变函数形参的值会引发一个编译时错误。这意味着你不能错误地改变形参的值。但是有时候,函数有一个形参值的变量副本是非常有用的。您可以指定一个或多个形参作为变量形参,从而避免在函数内部为自己定义一个新的变量。变量参数是变量而非常量,并给函数一个可修改的形参值副本。
    func alignRight(var string: String, count: Int, pad: Character) -> String {
        let amountToPad = count - countElements(string)
        for _ in 1...amountToPad {
            string = pad + string
        }
        return string
    }
    let originalString = "hello"
    let paddedString = alignRight(originalString, 10, "-")
    // paddedString is equal to "-----hello"
    // originalString is still equal to "hello"

In-Out 形参

如上描述,变量形参只能在函数本身内改变。如果你想让函数改变形参值,并想要在函数调用结束后保持形参值的改变,那你可以把形参定义为 in-out 形参。通过在形参定义的开始添加 inout 关键字来编写 in-out 形参。In-Out 形参有一个传递至函数的值,由函数修改,并从函数返回来替换原来的值。你只能传递一个变量作为 in-out 形参对应的实参。你不能传递一个常量或者字面量作为实参,因为常量和字面量不能被修改。当你把变量作为实参传递给 in out 形参时,需要在直接在变量前添加 & 符号,以表明它可以被函数修改。

提示:in-out 参数不能有默认值,可变参数的参数也不能被标记为 inout。如果您标记参数为 inout,它不能同时被标记为 var 或 let。

    func swapTwoInts(inout a: Int, inout b: Int) {
        let temporaryA = a
        a = b
        b = temporaryA
    }
swapTwoInts函数只是简单地交换a、b的值。该函数通过存储一个名为temporaryA临时常量的值,指定b的值到a,然后分配temporaryA到b执行该交换。你可以通过两个Int类型的变量调用swapTwoInts函数,从而交换它们的值。需要注意的是当它们被传递给swapTwoInts函数时,someInt和anotherInt名称前要加上前缀符号&:
    var someInt = 3
    var anotherInt = 107
    swapTwoInts(&someInt, &anotherInt)
    println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
    // prints "someInt is now 107, and anotherInt is now 3"

函数类型

闭包

闭包可以捕获和存储其所在上下文中任意常量和变量的引用。这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的内存操作。在 函数 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:

1.全局函数是一个有名字但不会捕获任何值的闭包

2.嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包

3.闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的没有名字的闭包

闭包表达式

闭包表达式的语法有如下形式:

{ (parameters) -> returnType in
    statements
}

闭包表达式语法可以使用常量、变量和 inout 类型作为参数,但不提供默认值。也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值。下面的例子展示了之前 backwards 函数对应的闭包表达式版本的代码:

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

需要注意的是内联闭包参数和返回值类型声明与 backwards 函数类型声明相同。在这两种方式中,都写成了 (s1: String, s2: String) -> Bool 类型。然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。闭包的函数体部分由关键字 in 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。

根据上下文推断类型

因为排序闭包是作为函数的参数进行传入的,Swift 可以推断其参数和返回值的类型。sort 期望第二个参数是类型为 (String, String) -> Bool 的函数,因此实际上 String, String 和 Bool 类型并不需要作为闭包表达式定义中的一部分。因为所有的类型都可以被正确推断,返回箭头 (->) 和 围绕在参数周围的括号也可以被省略:

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

单行表达式闭包可以省略 return

单行表达式闭包可以通过隐藏 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

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

参数名简写

Swift 自动为内联函数提供了参数名称简写功能,您可以直接通过 $0,$1,$2 等名字来引用的闭包的参数的值。如果您在闭包表达式中使用参数名称简写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称简写的类型会通过函数类型进行推断。in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

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

Trailing 闭包

如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用 trailing 闭包来增强函数的可读性。Trailing 闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个参数调用。

    func someFunctionThatTakesAClosure(closure: () -> ()) {
        // 函数体部分
    }

    // 以下是不使用 trailing 闭包进行函数调用

    someFunctionThatTakesAClosure({
        // 闭包主体部分
    })

    // 以下是使用 trailing 闭包进行函数调用

    someFunctionThatTakesAClosure() {
        // 闭包主体部分
    }

在上例中作为 sort 函数参数的字符串排序闭包可以改写为:

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

捕获(Capture)

闭包可以在其定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。Swift 最简单的闭包形式是嵌套函数,也就是定义在其他函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。下例为一个叫做 makeIncrementor 的函数,其包含了一个叫做 incrementor 嵌套函数。嵌套函数 incrementor 从上下文中捕获了两个值,runningTotal 和 amount。之后 makeIncrementor 将 incrementor 作为闭包返回。每次调用 incrementor 时,其会以 amount 作为增量增加 runningTotal 的值。

    func makeIncrementor(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementor() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementor
    }
incrementor 函数并没有获取任何参数,但是在函数体内访问了 runningTotal 和 amount 变量。这是因为其通过捕获在包含它的函数体内已经存在的runningTotal 和 amount 变量而实现。

由于没有修改 amount 变量,incrementor 实际上捕获并存储了该变量的一个副本,而该副本随着 incrementor 一同被存储。然而,因为每次调用该函数的时候都会修改 runningTotal 的值,incrementor 捕获了当前 runningTotal 变量的引用,而不是仅仅复制该变量的初始值。捕获一个引用保证了当 makeIncrementor 结束时候并不会消失,也保证了当下一次执行 incrementor 函数时,runningTotal 可以继续增加。

下面为一个使用 makeIncrementor 的例子:

let incrementByTen = makeIncrementor(forIncrement: 10)
该例子定义了一个叫做 incrementByTen 的常量,该常量指向一个每次调用会加10的 incrementor 函数。调用这个函数多次可以得到以下结果:
incrementByTen() // 返回的值为10
incrementByTen() // 返回的值为20
incrementByTen() // 返回的值为30

如果您创建了另一个 incrementor,其会有一个属于自己的独立的 runningTotal 变量的引用。下面的例子中,incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和 incrementByTen 中捕获的变量没有任何联系:

let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven() // 返回的值为7
incrementByTen() // 返回的值为40
上一页
下一页