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