函数是执行特定任务的自包含代码块。你给一个函数起一个名字来标识它做什么,这个名字被用来在需要的时候“调用”这个函数来执行它的任务。
Swift的统一函数语法足够灵活,可以表达任何内容,从没有参数名的简单c风格函数到包含每个参数的名称和参数标签的复杂objective - c风格方法。参数可以提供缺省值来简化函数调用,并且可以作为in-out参数传递,当函数完成执行后,in-out参数将修改传递的变量。
Swift中的每个函数都有一个类型,包括函数的参数类型和返回类型。您可以像Swift中的任何其他类型一样使用这种类型,这使得将函数作为参数传递给其他函数以及从函数返回函数变得很容易。函数也可以在其他函数中编写,以将有用的功能封装在嵌套函数范围内。
Defining and Calling Functions 定义和调用函数
当您定义一个函数时,您可以选择定义一个或多个命名的、类型化的值,函数将这些值作为输入,称为参数。您还可以选择定义一种类型的值,函数在完成时将作为输出返回,称为返回类型。
每个函数都有一个函数名,它描述函数执行的任务。要使用一个函数,您可以使用它的名称“调用”该函数,并将匹配函数参数类型的输入值(称为参数)传递给它。函数的参数必须始终以与函数的参数列表相同的顺序提供。
下面示例中的函数称为greet(person:),因为它就是这样做的——它接受一个人的名字作为输入,并为这个人返回一个问候语。要实现这一点,您需要定义一个输入参数—一个名为person的字符串值和一个返回类型的字符串,其中包含对该人的问候语:
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
所有这些信息都包含在函数定义中,函数定义的前缀是func关键字。您使用返回箭头->(一个连字符后面跟着一个右尖括号)指示函数的返回类型,该箭头后面跟着要返回的类型的名称。
定义描述了函数做什么,它期望接收什么,以及当它完成时返回什么。这个定义使得函数可以很容易地从代码的其他地方调用:
print(greet(person: "Anna"))
// Prints "Hello, Anna!"
print(greet(person: "Brian"))
// Prints "Hello, Brian!"
通过在person参数标签后面传递一个字符串值来调用greet(person:)函数,例如greet(person: "Anna")。因为函数返回一个字符串值,所以greet(person:)可以封装在对print(_:separator:terminator:)函数的调用中,以打印该字符串并查看其返回值,如上所示。
函数的主体首先定义一个名为greeting的新字符串常量,并将其设置为一个简单的greeting消息。然后使用return关键字将此问候语传递回函数。在表示返回问候语的代码行中,函数完成其执行并返回问候语的当前值。
您可以使用不同的输入值多次调用greet(person:)函数。上面的例子显示了如果用输入值“Anna”和输入值“Brian”调用它会发生什么。该函数在每种情况下都返回一个定制的问候语。
为了使这个函数的主体更短,您可以将消息创建和返回语句合并为一行:
func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// Prints "Hello again, Anna!"
Function Parameters and Return Values 函数的参数和返回值
函数参数和返回值在Swift中非常灵活。您可以定义任何东西,从具有单个未命名参数的简单实用函数到具有表示参数名称和不同参数选项的复杂函数。
Functions Without Parameters 无参数函数
函数不需要定义输入参数。这是一个没有输入参数的函数,它总是返回相同的字符串消息时,它被调用:
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// Prints "hello, world"
函数定义在函数名之后仍然需要括号,即使它不接受任何参数。当调用函数时,函数名后面还会跟着一对空括号。
Functions With Multiple Parameters 多个参数的函数
函数可以有多个输入参数,这些参数在函数的括号中编写,用逗号分隔。
这个函数接受一个人的名字,以及他们是否已经作为输入被问候,并返回一个适当的问候给这个人:
func greet(person: String, alreadyGreeted: Bool) -> String {
if alreadyGreeted {
return greetAgain(person: person)
} else {
return greet(person: person)
}
}
print(greet(person: "Tim", alreadyGreeted: true))
// Prints "Hello again, Tim!"
调用greet(person: alreadygreet:)函数时,要同时传递一个标记为person的字符串参数值和一个标记为alreadygreet的Bool参数值,该参数值用逗号分隔。注意,这个函数不同于前面一节中显示的greet(person:)函数。虽然这两个函数的名称都以greet开头,但是greet(person: alreadygreet:)函数接受两个参数,而greet(person:)函数只接受一个参数。
Functions Without Return Values 没有返回值的函数
函数不需要定义返回类型。下面是greet(person:)函数的一个版本,它打印自己的字符串值,而不是返回它:
func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
// Prints "Hello, Dave!"
由于不需要返回值,函数的定义不包括返回箭头(->)或返回类型。
注意:严格地说,这个版本的greet(person:)函数仍然返回一个值,即使没有定义返回值。没有定义返回类型的函数返回一个类型为Void的特殊值。这只是一个空元组,它被写成()。
函数的返回值在调用时可以忽略:
func printAndCount(string: String) -> Int {
print(string)
return string.count
}
func printWithoutCounting(string: String) {
let _ = printAndCount(string: string)
}
printAndCount(string: "hello, world")
// prints "hello, world" and returns a value of 12
printWithoutCounting(string: "hello, world")
// prints "hello, world" but does not return a value
第二个函数printWithoutCounting(string:)调用第一个函数,但是忽略它的返回值。当调用第二个函数时,消息仍然由第一个函数打印,但是没有使用返回的值。
返回值可以忽略,但是声明将返回值的函数必须始终这样做。具有定义的返回类型的函数不能允许控件在不返回值的情况下从函数底部掉落,如果尝试这样做,将导致编译时错误。
Functions with Multiple Return Values 多个返回值的函数
可以使用元组类型作为函数的返回类型,作为一个复合返回值的一部分返回多个值。
下面的例子定义了一个名为minMax(array:)的函数,它查找整型值数组中最小和最大的数:
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1.. currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
minMax(array:) 函数的作用是:返回一个包含两个Int值的元组。这些值被标记为min和max,以便在查询函数的返回值时可以通过名称访问它们。
函数的主体首先将名为currentMin和currentMax的两个工作变量设置为数组中第一个整数的值。然后,该函数遍历数组中剩余的值,并检查每个值,看看它是否小于或大于currentMin和currentMax的值。最后,整体最小值和最大值作为两个Int值的元组返回。
因为元组的成员值是作为函数返回类型的一部分命名的,所以可以使用点语法访问它们来检索找到的最小值和最大值:
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
// Prints "min is -6 and max is 109"
注意,元组的成员不需要在函数返回元组时进行命名,因为它们的名称已经作为函数返回类型的一部分指定了。
Optional Tuple Return Types 可选元组返回类型
如果要从函数返回的元组类型可能对整个元组“没有值”,则可以使用可选的元组返回类型来反映整个元组可以为nil的事实。通过在元组类型的右括号后面加上问号(Int, Int),可以编写一个可选的元组返回类型。或者(String, Int, Bool)?
注意:一个可选的元组类型,如(Int, Int)?与包含可选类型(Int?,Int ?)元素的元组不同。对于可选的元组类型,整个元组是可选的,而不仅仅是元组中的每个单独值。
上面的minMax(array:)函数返回一个包含两个Int值的元组。但是,函数不会对它传递的数组执行任何安全检查。如果数组参数包含一个空数组,那么上面定义的minMax(array:)函数将在试图访问数组[0]时触发运行时错误。
为了安全地处理一个空数组,用一个可选的元组返回类型编写minMax(array:)函数,当数组为空时返回一个nil值:
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.. currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
您可以使用可选绑定检查minMax(array:)函数的这个版本是否返回实际的元组值或nil:
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
// Prints "min is -6 and max is 109"
Function Argument Labels and Parameter Names 函数的参数标签和参数名
每个函数参数都有一个参数标签和一个参数名称。在调用函数时使用参数标签;每个参数都写在函数调用中,它的参数标签在它之前。参数名用于函数的实现。默认情况下,参数使用它们的参数名称作为参数标签。
func someFunction(firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(firstParameterName: 1, secondParameterName: 2)
所有参数必须有唯一的名称。虽然多个参数可能具有相同的参数标签,但惟一的参数标签有助于提高代码的可读性。
Specifying Argument Labels 指定参数标签
可以在参数名之前写一个参数标签,用空格隔开:
func someFunction(argumentLabel parameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
}
下面是 greet(person:) 函数的一个变体,它接受一个人的名字和家乡,并返回一个问候语:
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill! Glad you could visit from Cupertino."
参数标签的使用可以允许以一种表达性的、类似句子的方式调用函数,同时仍然提供一个可读的、意图清晰的函数体。
Omitting Argument Labels 省略参数标签
如果不需要参数的参数标签,请为该参数编写下划线(_),而不是显式的参数标签。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(1, secondParameterName: 2)
如果参数有参数标签,那么在调用函数时必须给参数贴上标签。
Default Parameter Values 默认参数值
您可以为函数中的任何参数定义默认值,方法是在该参数的类型之后为该参数赋值。如果定义了默认值,则可以在调用函数时省略该参数。
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// If you omit the second argument when calling this function, then
// the value of parameterWithDefault is 12 inside the function body.
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
将没有默认值的参数放在函数的参数列表的开头,然后是具有默认值的参数。没有默认值的参数通常对函数的意义更重要——首先编写它们可以更容易地识别调用的是同一个函数,而不管是否省略了任何默认参数。
Variadic Parameters 可变参数
可变参数接受指定类型的零个或多个值。您可以使用可变参数来指定参数可以在调用函数时传递不同数量的输入值。通过在参数的类型名称后面插入三个句点字符(…)来编写可变参数。
传递给可变参数的值在函数体中作为适当类型的数组可用。例如,一个具有数字名称和Double类型的可变参数…在函数体中可用一个称为[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)
平均值为 3.0
arithmeticMean(3, 8.25, 18.75)
平均值为 10.0
注意:一个函数最多可以有一个可变参数。
In-Out Parameters 输入输出参数
函数参数默认为常量。试图从函数体中更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,则将该参数定义为in-out参数。
通过在参数的类型前放置in-out关键字,可以编写in-out参数。in-out参数有一个值,该值传递给函数,由函数修改,然后传递回函数,以替换原始值。有关in-out参数的行为和相关编译器优化的详细讨论,请参见in-out参数。
您只能将 变量 作为in-out参数的参数传递。
不能将常量或文字值作为参数传递,因为常量和文字不能修改。
当您将&作为参数传递给in-out参数时,您将在变量的名称前直接放置一个&,以指示该函数可以修改它。
In-out参数不能具有默认值,并且可变参数不能标记为in-out。
下面是一个名为swapTwoInts(::)的函数的例子,它有两个in-out整数参数,分别称为a和b:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoInts(::)函数简单地将b的值转换为a,将a的值转换为b。该函数将a的值存储在名为temporaryA的临时常量中,然后将b的值赋值给a,然后再将temporaryA赋值给b,从而执行这种转换。
您可以使用两个Int类型的变量调用swapTwoInts(::)函数来交换它们的值。注意,someInt和anotherInt的名称在传递给swapTwoInts(::)函数时,前面都加了&。
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
上面的例子表明someInt和anotherInt的原始值是由swapTwoInts(::)函数修改的,即使它们最初是在函数外部定义的。
注意:In-out参数与从函数返回值不同。上面的swapTwoInts示例没有定义返回类型或返回值,但是它仍然修改someInt和anotherInt的值。In-out参数是函数在其函数体范围之外产生影响的另一种方法。
Function Types 函数类型
每个函数都有一个特定的函数类型,由函数的参数类型和返回类型组成。
例如:
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
这个例子定义了两个简单的数学函数addTwoInts和multiplyTwoInts。这些函数都接受两个Int值,并返回一个Int值,这是执行适当数学运算的结果。
这两个函数的类型都是(Int, Int) -> Int。
一个函数有两个参数,都是Int类型的,并且返回一个Int类型的值。
下面是另一个例子,对于没有参数或返回值的函数:
func printHelloWorld() {
print("hello, world")
}
这个函数的类型是()-> Void,或者“一个没有参数,返回Void的函数”。
Using Function Types 使用函数类型
你使用函数类型就像Swift中的其他类型一样。例如,您可以定义一个常量或变量为函数类型,并为该变量分配一个适当的函数:
var mathFunction: (Int, Int) -> Int = addTwoInts
这可以理解为:
定义一个名为mathFunction的变量,它的类型是'一个函数,它接受两个Int值,并返回一个Int值。'设置这个新变量来引用名为addTwoInts的函数。"
addTwoInts(::)函数具有与mathFunction变量相同的类型,因此Swift的类型检查器允许该赋值。
现在,您可以使用mathFunction调用指定的函数:
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"
可以将具有相同匹配类型的不同函数赋给相同的变量,方法与非函数类型相同:
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"
与任何其他类型一样,当你将一个函数赋值给一个常量或变量时,你可以让Swift来推断函数类型:
let anotherMathFunction = addTwoInts
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
Function Types as Parameter Types 函数类型作为参数类型
可以使用函数类型(Int, Int) -> Int作为另一个函数的参数类型。这使您能够将函数实现的某些方面留给函数调用者在调用函数时提供。
下面是一个从上面打印数学函数结果的例子:
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// Prints "Result: 8"
这个例子定义了一个名为printMathResult(::_:)的函数,它有三个参数。第一个参数称为mathFunction,类型为(Int, Int) -> Int.您可以将该类型的任何函数作为第一个参数的参数传递。第二个和第三个参数分别称为a和b,它们都是Int类型。
调用printMathResult(:::)时,将传递addTwoInts(:_:)函数和整数值3和5。它使用值3和5调用提供的函数,并输出值8的结果。
printMathResult(:::)的作用是打印对适当类型的数学函数的调用的结果。函数的实现实际上做什么并不重要——重要的是函数的类型是否正确。这使得printMathResult(:::)能够以一种类型安全的方式将它的一些功能传递给函数的调用者。
Function Types as Return Types 函数类型作为返回值
可以使用函数类型作为另一个函数的返回类型。您可以在返回函数的返回箭头(->)之后立即编写一个完整的函数类型。
下一个示例定义了两个简单的函数,分别称为stepForward(:)和stepback(:)。stepForward(:)函数返回的值比它的输入值多一个,而stepback(:)函数返回的值比它的输入值少一个。两个函数都有一个类型(Int) -> Int:
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
这是一个名为chooseStepFunction(back:)的函数,它的返回类型是(Int) -> Int。
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
您现在可以使用chooseStepFunction(backward:)来获得一个函数,它将步进一个或另一个方向:
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function
上面的例子确定了将一个名为currentValue的变量逐步移动到更接近于零的位置需要一个正的步骤还是负的步骤。currentValue的初始值为3,这意味着currentValue > 0返回true,从而导致chooseStepFunction(back:)返回stepback(_:)函数。对返回函数的引用存储在一个名为moveNearerToZero的常量中。
现在moveNearerToZero指的是正确的函数,它可以用来从倒数到零:
print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
Nested Functions 嵌套函数
到目前为止,您在本章中遇到的所有函数都是全局函数的例子,它们都是在全局范围内定义的。您还可以在其他函数的主体中定义函数,称为嵌套函数。
默认情况下,嵌套函数对外部世界是隐藏的,但是仍然可以由它们的封闭函数调用和使用。封闭函数还可以返回它的一个嵌套函数,以允许嵌套函数在另一个范围中使用。
您可以重写上面的chooseStepFunction(backward:)示例来使用和返回嵌套函数:
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!