函数
函数是执行特定任务的自包含代码块。你给一个函数一个名字来标识它做了什么,这个名字用来“调用”函数在需要时执行它的任务。
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参数标签之后传递一个String值,例如greet(person:“Anna”),可以调用greet(person :)函数。 因为该函数返回一个字符串值,所以可以通过调用print(_:separator:terminator :)函数来包装greet(person :)以打印该字符串并查看其返回值,如上所示。
注意
print(_:separator:terminator :)函数没有第一个参数的标签,其他参数是可选的,因为它们有一个默认值。函数语法的这些变体在下面的函数参数标签和参数名称以及默认参数值中讨论。
greet(person :)函数的主体首先定义一个名为greeting的新String常量并将其设置为简单的问候消息。然后使用return关键字将此问候传回给函数。在返回问候语的代码行中,该函数完成其执行并返回当前的问候语的值。
您可以使用不同的输入值多次调用greet(person :)功能。上面的示例显示了如果使用输入值“Anna”和输入值“Brian”调用会发生什么情况。该函数在每种情况下返回一个定制的问候语。
为了使此函数的主体更短,可以将消息创建和return语句组合到一行中:
func greetAgain(person: String) -> String {
return"Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// Prints "Hello again, Anna!"
函数参数和返回值
函数参数和返回值在Swift中非常灵活。 您可以使用具有单个未命名参数的简单实用程序函数来定义任何具有表达参数名称和不同参数选项的复杂函数。
没有参数的函数
函数不需要定义输入参数。 这里有一个没有输入参数的函数,它总是在调用时返回相同的String消息:
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的String参数值和一个标记为already的Bool参数值(用逗号分隔)来调用greet(person:alreadyGreeted :)函数。 请注意,此功能与前面部分中显示的greet(person :)函数不同。 虽然这两个函数的名字都以greet开头,但是greet(person:alreadyGreeted :)函数有两个参数,但是greet(person :)函数只有一个参数。
没有返回值的函数
函数不需要定义返回类型。 这是一个greet(person :)函数的版本,它打印自己的String值而不是返回它:
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
第一个函数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 valueinarray[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
minMax(array :)函数返回一个包含两个Int值的元组。 这些值被标记为最小值和最大值,以便在查询函数的返回值时可以按名称访问它们。
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"
可选的元组返回类型
如果要从函数返回的元组类型可能对整个元组有“无价值”,那么可以使用可选的元组返回类型来反映整个元组可以为零的事实。通过在元组类型的右括号后面放置一个问号(如(Int,Int)),可以编写可选的元组返回类型?或(String,Int,Bool)?.
注意
一个可选的元组类型,如(Int,Int)?与包含可选类型(例如(Int?,Int?))的元组不同。使用可选的元组类型,整个元组是可选的,而不仅仅是元组中的每个单独的值。
上面的minMax(array :)函数返回一个包含两个Int值的元组。但是,函数不会对它传递的数组执行任何安全检查。如果数组参数包含一个空数组,则在尝试访问数组[0]时,上面定义的minMax(array :)函数将触发运行时错误。
要安全地处理空数组,请使用可选的元组返回类型编写minMax(array :)函数,并在数组为空时返回nil值:
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { returnnil }
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 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(argumentLabelparameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
}
这是一个变化的greet(person:)功能,需要一个人的名字和家乡,并返回一个问候语:
func greet(person: String, fromhometown: 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
在具有默认值的参数之前,将没有默认值的参数放置在函数参数列表的开头。没有默认值的参数通常对函数的意义更重要 - 首先编写它们可以更容易地识别出调用了相同的函数,而不管是否省略了任何默认参数。
可变参数
可变参数接受指定类型的零个或多个值。您可以使用可变参数来指定在调用函数时可以传递不同数量的输入值。通过在参数的类型名称后插入三个句点字符(...)来写入可变参数。
传递给可变参数的值可以在函数体内作为适当类型的数组使用。例如,名称为数字和Double ...类型的可变参数在函数的主体内可用作常量数组,称为数字类型[Double]。
以下示例计算任意长度的数字列表的算术平均值(也称为平均值):
func arithmeticMean(_numbers: Double...) -> Double {
var total: Double = 0
for numberinnumbers {
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 Parameters
函数参数默认是常量。试图从该函数体内更改函数参数的值会导致编译时错误。这意味着你不能错误地改变参数的值。如果您想要一个函数来修改参数的值,并且希望这些更改在函数调用结束后保持不变,请将该参数定义为输入输出In-Out参数。
通过将inout关键字放在参数的类型之前,可以编写一个in-out参数。输入输出参数具有传递给函数的值,由函数修改并从函数返回以替换原始值。有关输入输出参数和相关编译器优化的详细讨论,请参阅输入输出参数。
您只能传递一个变量作为输入参数的参数。您不能传递常量或文字值作为参数,因为常量和文字不能被修改。当您将一个&符号(&)直接作为参数传递给一个输入参数时,它将直接放在变量的名称之前,以表明它可以被该函数修改。
注意
输入输出参数不能有默认值,可变参数不能标记为输入。
下面是一个名为swapTwoInts(_:_ :)的函数的示例,该函数有两个输入的整数参数,分别称为a和b:
func swapTwoInts(_a: inoutInt, _b: inoutInt) {
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(_:_ :)函数修改,尽管它们最初是在函数之外定义的。
注意
输入输出参数与从函数返回值不同。 上面的swapTwoInts示例没有定义返回类型或返回值,但它仍然修改someInt和anotherInt的值。 输入输出参数是函数在函数体范围之外产生效果的替代方法。
函数类型
每个函数都有一个特定的函数类型,由函数的参数类型和返回类型组成。
例如:
func addTwoInts(_a: Int, _b: Int) -> Int {
returna + b
}
func multiplyTwoInts(_a: Int, _b: Int) -> Int {
returna * 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作为另一个函数的参数类型。 这使您可以为函数的调用者保留函数实现的某些方面,以提供该函数何时被调用。
下面是一个打印上面的数学函数结果的例子:
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。print
printMathResult(_:_:_ :)的作用是将调用的结果打印到适当类型的数学函数中。这个函数的实现实际上并不重要,只是函数的类型是正确的。这使printMathResult(_:_:_ :)能够以类型安全的方式将其某些功能交给函数的调用者。
函数类型作为返回类型
您可以使用函数类型作为另一个函数的返回类型。您可以通过在返回函数的返回箭头( - >)之后立即写入完整的函数类型来完成此操作。
下一个示例定义了两个简单的函数,分别称为stepForward(_ :)和stepBackward(_ :)。 stepForward(_ :)函数的返回值比其输入值多一个,而stepBackward(_ :)函数返回的值比其输入值小1。这两个函数都有一个类型(Int) - > Int:
func stepForward(_input: Int) -> Int {
returninput + 1
}
func stepBackward(_input: Int) -> Int {
returninput - 1
}
这里有一个名为chooseStepFunction(backward :)的函数,它的返回类型是(Int) - > Int。 chooseStepFunction(backward :)函数根据名为backward的布尔参数返回stepForward(_ :)函数或stepBackward(_ :)函数:
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
你现在可以使用chooseStepFunction(向后:)来获得一个函数,这个函数将沿着一个方向或另一个方向进行:
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 { returninput + 1 }
func stepBackward(input: Int) -> Int { returninput - 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!