函数是执行特定任务的自包含代码块。我们给一个函数起一个名字来标识它做什么,这个名字在需要的时候用来“调用”函数来执行它的任务。
Swift的统一函数语法足够灵活,从没有参数名的简单C风格函数到每个参数都有名称和参数标签的复杂Objective-C风格方法,可以表达任何东西。参数可以提供默认值来简化函数调用,并且参数可以作为输入输出参数传递,一旦函数完成其执行,这些输入输出参数就会修改传递的变量。
Swift中的每个函数都有一个类型,由函数的参数类型和返回类型组成。我们可以像Swift中的任何其他类型一样使用此类型,这样可以很容易地将函数作为参数传递给其他函数,并从函数返回函数。也可以在其他函数中编写函数,将有用的功能封装在嵌套的函数范围内。
定义和调用函数
定义函数时,可以选择定义一个或多个命名的类型化值用作函数的输入,这个值称为参数。我们还可以选择定义一种值类型,当函数完成时,它将作为输出传回,称为其返回类型。
每个函数都有一个函数名,用于描述函数执行的任务。要使用函数,我们可以用函数名“调用”该函数,并将与函数参数类型匹配的输入值(称为参数)传递给它。函数的参数必须始终按照与函数的参数列表相同的顺序提供。
下面示例中的函数名为greet(person:)
,它将一个人的名字作为输入,并为该人返回一个问候语。要实现这一点,我们需要定义一个输入参数—一个名为person的String类型的值和一个返回类型String,即对此人的问候语:
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:)
函数打印该字符串并且可以看到其返回值,如上所示。
注意
print(_:separator:terminator:)
函数的第一个参数没有标签,其他参数是可选的,因为它们有默认值。函数语法的这些变体将在下面的函数参数标签、参数名称和默认参数值中讨论。
greet(person:)
函数的主体首先定义一个名为greeting的新字符串常量,并将其设置为简单的问候语消息。然后使用return关键字将greeting作为函数的返回值。在表示return greeting
的代码行中,函数完成其执行并返回greeting的当前值。
可以使用不同的输入值多次调用greet(person:)
函数。上面的例子显示了如果用输入值“Anna”和输入值“Brian”调用它会发生什么。函数在每种情况下都返回一个定制的问候语。
要缩短此函数的正文,可以将消息创建和return语句合并到一行中:
func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// Prints "Hello again, Anna!"
函数参数和返回值
函数参数和返回值在Swift中非常灵活。从具有单个未命名参数的简单实用的函数到具有表示性参数名称和不同参数选项的复杂函数,我们可以定义任何内容,。
无参数函数
函数不需要定义输入参数。这是一个没有输入参数的函数,每当调用它时,它总是返回相同的字符串消息:
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// Prints "hello, world"
函数定义仍然需要在函数名后面加括号,即使它不需要任何参数。调用函数时,函数名后面还会跟一对空括号。
多参数函数
函数可以有多个输入参数,这些参数写在函数的括号内,用逗号分隔。
此函数将某人的姓名和是否已被问候作为输入,并为此人返回适当的问候语:
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!"
我们通过传递用逗号分隔的一个标记为person的字符串参数值和一个标记为alreadygreed的Bool参数值来调用greet(person:alreadyGreeted:)
函数。请注意,此函数不同于前面一节中显示的greet(person:)
函数。尽管这两个函数的名称都以greet开头,但greet(person:alreadyGreeted:)
函数接受两个参数,但greet(person:)
函数只接受一个参数。
没有返回值的函数
函数不需要定义返回类型。下面是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 doesn't return a value
第一个函数printAndCount(string:)
打印一个字符串,然后将其字符数返回为Int类型。第二个函数printWithoutCounting(string:)
调用第一个函数,但忽略其返回值。调用第二个函数时,第一个函数仍打印消息,但不使用返回值。
注意
返回值可以忽略,但如果表示出会有返回值的函数就必须始终有返回值。具有已定义的返回类型的函数不允许函数是不返回值的情况,尝试这样做将导致编译时错误。
具有多个返回值的函数
可以使用元组类型作为函数的返回类型,这样可以将多个值作为一个复合返回值的一部分来返回。
下面的示例定义了一个名为minMax(array:)
的函数,用于查找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)
}
minMax(array:)
函数返回一个包含两个Int值的元组。这些值被标记为min和max,以便在查询函数的返回值时可以通过名称访问它们。
minMax(array:)
函数的主体首先将两个局部变量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"
请注意,元组的成员不需要在元组从函数返回时命名,因为它们的名称已经作为函数返回类型的一部分。
可选元组返回类型
如果从函数返回的元组类型可能使整个元组没有值的话,则可以使用可选的元组返回类型来反映整个元组可以为nil的情况。我们可以通过在元组类型的右括号后面放置问号来编写可选的元组返回类型,例如(Int, Int)?
或者(String, Int, Bool)?
。
注意
可选的元组类型,例如(Int, Int)?
与包含可选类型的元组,例如(Int?, Int?)
,不同。对于可选的tuple类型,整个元组是可选的,而不仅仅是元组中的每个值是可选的。
上面的minMax(array:)
函数返回一个包含两个Int值的元组。但是,函数不会对它传递的数组执行任何安全检查。如果array参数包含空数组,则上述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..<array.count] {
if value < currentMin {
currentMin = value
} else if value > 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"
隐式返回的函数
如果整个函数体是一个表达式,则函数将隐式返回该表达式。例如,以下两个函数具有相同的行为:
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// Prints "Hello, Dave!"
func anotherGreeting(for person: String) -> String {
return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))
// Prints "Hello, Dave!"
greeting(for:)
函数的整个定义是它返回的问候语消息,这意味着它可以使用这种较短的形式。anotherGreeting(for:)
函数使用return关键字返回相同的问候语消息,就像一个较长的函数一样。任何只作为一个返回行编写的函数都可以忽略return
关键字。
正如我们将在快捷Getter
声明中看到的,属性Getter
就可以使用隐式返回。
注意
作为隐式返回值编写的代码是必须要返回一些值的。例如,不能使用fatalError("Oh no!")
或print(13)
这种不会返回值的函数来作为隐式返回值。
函数参数标签和参数名称
每个函数参数都有一个参数标签和一个参数名。参数标签在调用函数时使用;每个参数都写在函数调用中,其参数标签在参数之前。参数名用于函数的实现。默认情况下,参数使用其参数名称作为参数标签。
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)
所有参数必须具有唯一的名称。尽管多个参数可能具有相同的参数标签,但唯一的参数标签有助于提高代码的可读性。
指定参数标签
在参数名称前写一个参数标签,用空格分隔:
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."
参数标签的使用可以让函数以一种表达性的、类似句子的方式被调用,同时仍然提供一个可读的、意图清晰的函数体。
省略参数标签
如果不需要参数的参数标签,请为该参数编写下划线(_)
来代替显式的参数标签。
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)
如果参数有参数标签,则在调用函数时必须使用标签标记参数。
默认参数值
通过在参数类型之后为参数赋值,可以为函数中的任何参数定义默认值。如果定义了默认值,则可以在调用函数时忽略该参数。
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
将没有默认值的参数放在函数参数列表的开头,也就是放在具有默认值的参数之前。对于函数的意义来说,没有默认值的参数通常更为重要,先编写它们可以更容易地识别调用的是同一个函数,而不用管是否省略了其他有默认只的参数。
可变参数
可变参数接受指定类型的零个或多个值。我们可以使用可变参数来指定在调用函数时可以向参数传递不同数量的输入值。通过在参数的类型名称后插入三个句点字符(…)来写入可变参数。
在函数体中,传递给可变参数的值作为适当类型的数组来用。例如,一个名为numbers、类型为Double...的可变参数,在函数体中作为[Double]类型的numbers常量数组来使用。
下面的示例计算任意长度的数字列表的算术平均值(也称为平均值):
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
可变参数后面的第一个参数必须有参数标签。参数标签可以明确哪些参数是传递给可变参数,哪些参数是传递给可变参数后面的参数。
一个函数可以有多个可变参数。
in-out参数
默认情况下,函数参数是常量。尝试从函数体中更改函数参数的值会导致编译时错误。这意味着我们不能错误地更改参数的值。如果希望让函数可以修改参数的值,并且希望这些更改在函数调用结束后保持不变,请将该参数定义为in-out参数。
我们可以通过将inout关键字放在参数的类型之前来编写in-out参数。in-out参数有一个值,该值传入函数,可以由函数修改,然后从函数中传回以替换原始值。有关输入输出参数的行为和相关编译器优化的详细讨论,请参阅输入输出参数。
只能将变量作为输入输出参数的参数传递。不能传递常量或文字值作为参数,因为常量和文字不能修改。当我们将变量作为参数传递给in-out参数时,可以在变量名称前直接放置一个符号(&),以指示函数可以修改它。
注意
in-out参数不能有默认值,并且可变参数不能标记为in-out参数。
下面是一个名为swapTwoInts(_:_:)
的函数示例,它有两个名为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参数是函数在其函数体范围之外产生效果的另一种方法。
函数类型
每个函数都有一个特定的函数类型,由函数的参数类型和返回值类型组成。
例如:
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的函数”
使用函数类型
我们使用函数类型就像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
函数类型作为参数类型
可以使用(Int, Int) -> Int
等函数类型作为另一个函数的参数类型。这使我们能够在调用函数时将函数实现的某些方面留给函数的调用方提供。
下面是一个打印上面d的函数结果的示例:
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类型的。它们被用作提供的mathFunction函数的两个输入值。
当调用printMathResult(_:_:_:)
时,将addTwoInts(_:_:)
函数以及整数值3和5传递给它。它用值3和5调用提供的函数,并打印8的结果。
printMathResult(_:_:_:)
的作用是打印调用适当类型的mathFunction函数的结果。不管函数的实现是什么,重要的是函数的类型是否正确。这使得printMathResult(_:_:_:)
能够以类型安全的方式将其某些功能传递给函数的调用者。
函数类型作为返回类型
可以使用函数类型作为另一个函数的返回类型。我们可以通过在返回函数的返回箭头(->)
之后立即编写完整的函数类型来完成此操作。
下一个示例定义了两个简单的函数,称为stepForward(_:)
和stepBackward(_:)
。stepForward(_:)
函数返回的值比其输入值大一个,stepBackward(_:)
函数返回的值比其输入值小一个。两个函数的类型都是(Int) -> Int
:
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
下面是一个名为chooseStepFunction(backward:)
的函数,其返回类型为(Int)-> Int
。chooseStepFunction(backward:)
函数基于一个名为backward
的布尔参数返回stepForward(_:)
或stepBackward(_:)
函数:
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(backward:)
返回stepBackward(_:)
函数。返回函数的引用存储在名为moveNearerToZero
的常量中。
现在moveNearerToZero
引用了正确的函数,可以使用它来计数为零:
print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
嵌套的函数
到目前为止,我们在本章中遇到的所有函数都是在全局范围内定义的全局函数的示例。我们还可以在其他函数体中定义函数,称为嵌套函数。
默认情况下,嵌套函数对外隐藏,但仍可以由其外层封闭函数调用和使用。外层封闭函数还可以返回一个其中的嵌套函数,以允许在另一个作用域中使用该嵌套函数。
我们可以重写上面的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!