Swift学习笔记八
函数
Swift的函数语法非常独特,也提供了很高的灵活性和可读性。它可以充分表达从简单的无参数C风格函数到复杂的拥有局部变量和外部变量的OC风格的方法。参数可以有默认值,方便函数的调用。Swift中的每个函数都有一个类型,由其参数类型和返回值类型组成,这个类型可以像Swift中的任何其他类型一样被使用,因此,函数被作为参数传递,或者从一个函数返回一个函数等。函数也可以嵌套函数形成嵌套链。
定义和调用函数
定义函数时,可以给它定义一个或多个参数和返回值类型,这些并不是必须的。比如:
func sayHello(personName: String) -> String { let greeting = "Hello, " + personName + "!" return greeting }
如果函数不需要返回值,那么“->type”就可以省略掉,其实此时函数仍然返回了一个特殊Void类型的值,它是一个空的元组(tuple)。
函数也可以同时返回多个值,这就是返回一个包含多个值的元组。比如:
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) }
因为函数返回的元组的元素是已经定义了名称的,因此可以通过点语法来获取它的元素,比如minMax([1,2,3]).min。在函数体里边返回元组的时候,不需要标明元素的名字,因为这些名字在函数定义的时候就已经给出了。
返回可选元组类型
如果返回的元组可能存在整个元组都没有值,则可使用可选元组返回类型(optional tuple return type)来表明这个返回值可能是nil。这种类型可以写为:(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([8, -6, 2, 109, 3, 71]) { println("min is \(bounds.min) and max is \(bounds.max)") } // prints "min is -6 and max is 109”
函数参数名称
函数的参数都可以指定名称,然后就可以在函数体里使用这些名称了:
func someFunction(parameterName: Int) { // function body goes here, and can use parameterName // to refer to the argument value for that parameter }
不过,这里的参数名只能在函数体内部使用,所以又称之为局部参数名称(local parameter name)。这些名称可以用来在表示传入参数的意义。如果希望函数调用者在调用函数时提供参数名,就可以在局部参数名称之外再定义外部参数名称(external 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(s1: String, s2: String, joiner: String) -> String { return s1 + joiner + s2 } join("hello", "world", ", ") //对比 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 { for character in string { if character == characterToFind { return true } } return false }
默认参数值
作为函数定义的一部分,你可以为参数设置默认值,如果参数具备默认值,那么在调用函数的时候可以省略该参数。
在设定参数默认值时,最佳实践是将具有默认值的参数放置在参数列表的最后,这样可以确保在多次函数调用的时候,不具备默认值的参数顺序都是保持一致的,这样就可以确定这些多次调用是调用的同一个函数。设定参数默认值的语法如下:
func join(string s1: String, toString s2: String, withJoiner joiner: String = " ") -> String { return s1 + joiner + s2 }
可以看出,如果为参数提供了默认值,那么最佳实践就是总是为设定了默认值的参数设定外部参数名称,这就使得在函数调用的时候,不能省略这个参数,保持了函数调用方式的一致性和可读性,为了简便,Swift提供了便捷的特性,就是给所有设定了默认值的参数自动加上了外部参数名称,这个外部参数名称和其局部参数名称是相同的,就相当于前面所讲的(#)符号,这就使得设定了默认值的参数在被调用时也必须显示地给出:
func join(s1: String, s2: String, joiner: String = " ") -> String { return s1 + joiner + s2 } join("hello", "world", joiner: "-")//这里必须显示给定joiner // returns "hello-world”
这里在调用函数的时候,如果你实在不想写给定了默认值的参数名,那么可以用下划线(_)代替显示给定的外部名称,不过并不推荐这样做。
可变参数
一个可变参数接收0个或多个某个指定类型的值。使用可变参数意味着在调用该函数时可以向该参数传递可变数目的值。可变参数的声明语法是在参数的类型名后边加上三个点(...),向可变参数传递的值在函数体内部被组合成了一个特定类型的数组,比如:
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.25, 18.75) // returns 10.0, which is the arithmetic mean of these three numbers
注意:一个函数最多只能有一个可变参数,并且它必须位于参数列表的最后,这是为了避免在函数调用的时候多参数时的混淆不清。如果函数有一个或者多个指定默认值的参数,同时又有可变参数,那么把可变参数放置在所有指定默认值的参数后边,即列表的最后边。
常量和变量参数
函数的参数默认都是常量,在函数内部尝试改变参数的值会触发编译错误,这使得误操作改变参数值不会发生。
但是,有时候需要函数有一个参数的变量副本,通过指定一个或多个参数为变量参数,就不用再在函数里边重新定义变量了。变量参数是作为变量而非常量使用,它提供了一个可改变的值副本来供你的函数操作。
通过在参数名称前面加上var关键字就可以定义一个变量参数:
func alignRight(var string: String, count: Int, pad: Character) -> String { let amountToPad = count - count(string) if amountToPad < 1 { return string } let padString = String(pad) for _ in 1...amountToPad { string = padString + string } return string } let originalString = "hello" let paddedString = alignRight(originalString, 10, "-") // paddedString is equal to "-----hello" // originalString is still equal to "hello”
注意:对变量参数的修改在函数调用结束之后就不存在了,并且变量参数在函数外部是不可见的,它只存在于函数调用的生命周期内。
输入输出参数(In-Out Parameters)
如前所述,变量参数是可以也只能在函数内部被改变,如果你希望函数改变一个参数,并且函数调用结束后仍然有效,就需要将那个参数定义为输入输出参数。通过在参数定义之前加上inout关键字,可以创建一个in-out参数。一个in-out参数有一个被传入函数的值,这个值在函数内部被改变,然后被传回到函数之外替换这个参数的初始值。只能用变量创建输入输出参数,常量和字面量都不可以。在调用函数时,在变量名称之前加上(&)号标明这个参数可以被函数修改:
func swapTwoInts(inout a: Int, inout b: Int) { let temporaryA = a a = b b = temporaryA } 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”
注意:in-out参数不能有默认值,可变参数(variadic parameters)也不能被标记为inout,如果标记了一个参数为inout,那么不能同时把它标记为var或者let了。
in-out参数并不同于函数的返回值,如上面的例子,函数并没有返回任何值,它是函数能够在函数体作用域外产生影响的一种有效方法。
函数类型(Function Types)
每个函数都有一个特定的函数类型,它是由函数的参数类型和返回值类型组成的。比如:
func addTwoInts(a: Int, b: Int) -> Int { return a + b } func multiplyTwoInts(a: Int, b: Int) -> Int { return a * b }
这两个简单的函数都接受两个Int类型参数,然后返回一个Int类型值,因此它们的函数类型都是 (Int, Int) -> Int 。这个读作“一个包含两个Int型参数并且返回一个Int类型值的函数类型”。如果函数没有参数,也不返回值,则函数类型就是 () -> () 。后边这对空括号表示函数返回Void,它在Swift中被表示为空的元组。
函数类型可以像Swift中的所有其他类型一样被使用,比如你可以定义一个某函数类型的变量或者常量,并且将某个合适的函数赋值给它:
var mathFunction: (Int, Int) -> Int = addTwoInts
然后就可以像调用普通函数一样把这个变量当做函数调用了。
只要函数类型是一样的,另一个不同的函数也可以被赋值给同一个变量。
作为参数类型的函数类型
函数类型同样也可以作为其他函数的参数类型,这就可以将函数的一部分逻辑实现交给函数的调用者去实现,然后作为参数传进函数。比如:
func printMathResult(mathFunction: (Int, Int) -> Int, a: Int, b: Int) { println("Result: \(mathFunction(a, b))") } printMathResult(addTwoInts, 3, 5) // prints "Result: 8”
这个例子中,mathFunction的实现是交给函数调用者实现的,比如传入的addTwoInts。
作为返回类型的函数类型
函数类型也可以作为返回类型被其他函数返回。比如:
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 }
注意这个chooseStepFunction函数的返回类型,它是一个函数类型((Int) -> Int),因此这个函数最终返回的其实就是一个函数。
嵌套函数
之前介绍的函数都是在全局作用域下定义的,因此都是全局函数。函数也可以在函数内部被定义,此时就是嵌套函数了,它的作用域在函数内部,对外默认是隐藏的。但是,当外层函数把嵌套函数当做返回值返回以后,外部环境就可以其他作用域调用这个内部嵌套函数了(闭包的概念)。比如,上面的例子,就可以用嵌套函数的形式实现:
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 } var currentValue = -4 let moveNearerToZero = chooseStepFunction(currentValue > 0) // moveNearerToZero now refers to the nested stepForward() function while currentValue != 0 { println("\(currentValue)... ") currentValue = moveNearerToZero(currentValue) } println("zero!") // -4... // -3... // -2... // -1... // zero!