引言
继续学习Swift文档,从上一章节:控制流,我们学习了Swift控制流相关的内容,如for-in循环、while循环、if语句、switch语句、continue、break、fallthrough、带标签的语句、guard语句这些内容。其中switch语句在swift里的用法与OC相比,有很大的差别,功能更丰富,具体的可以参阅上一章节的内容。现在,我们学习Swift的函数相关的内容。这一章节的内容很重要了,几乎所有开发语言都离不开函数的使用,所以我们要认真对待并熟练掌握这一章节的内容。由于篇幅较长,这里分篇来记录,接下来,Fighting!
如果你已经熟练掌握了函数的使用,那么请参阅下一章节:闭包
函数
函数是执行特定任务的独立代码块。 您为函数指定一个名称,该名称可以标识其功能,并且该名称用于“调用”该函数以在需要时执行其任务。
Swift的统一函数语法足够灵活,可以表达任何内容,从没有参数名称的简单C样式函数到具有每个参数名称和参数标签的复杂的Objective-C样式方法。 参数可以提供默认值以简化函数调用,并且可以作为输入-输出参数传递,参数在函数完成执行后会修改传递的变量。
Swift中的每个函数都有一个类型,包括该函数的参数类型和返回类型。 您可以像Swift中的任何其他类型一样使用此类型,这使得将函数作为参数传递给其他函数以及从函数返回函数变得容易。 也可以在其他函数中编写函数,以将有用的函数封装在嵌套函数范围内。
1 定义和调用函数
定义函数时,可以选择定义一个或多个该函数作为输入的命名,键入的值,称为参数。 您还可以选择定义一种值类型,该函数完成后将作为输出传回,称为函数的返回类型。
每个功能都有一个功能名称,该名称描述了该功能执行的任务。 要使用函数,请使用函数名称“调用”该函数,然后将与函数参数类型匹配的输入值(称为自变量)传递给它。 必须始终以与函数参数列表相同的顺序提供函数的参数。
在下面的示例中,该函数称为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 :)函数,例如greet(person:“ Anna”)。 由于该函数返回一个String值,因此可以将greet(person :)包装在对print(_:separator:terminator :)函数的调用中,以打印该字符串并查看其返回值,如上所示。
注意
print(_:separator:terminator :)函数的第一个参数没有标签,其他参数是可选的,因为它们具有默认值。 有关函数语法的这些变体,将在下面的“函数参数标签”,“参数名称”和“默认参数值”中进行讨论。
greet(person :)函数的主体通过定义一个新的String常量(称为greeting)并将其设置为简单的问候消息开始。 然后,使用return关键字将此greeting传回函数。 在表示返回greeting的代码行中,该函数完成其执行并返回greeting的当前值。
您可以使用不同的输入值多次调用greet(person :)函数。 上面的示例显示了如果使用输入值“ Anna”和输入值“ Brian”进行调用会发生的情况。 该函数在每种情况下都会返回定制的问候语。
为了使该函数的主体更短,可以将消息创建和return语句合并为一行:
func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// Prints "Hello again, Anna!"
2 函数参数和返回值
函数参数和返回值在Swift中非常灵活。 您可以定义任何东西,从具有单个未命名参数的简单实用程序函数到具有表达性参数名称和不同参数选项的复杂函数。
2.1 没有参数的函数
不需要功能来定义输入参数。 这是一个没有输入参数的函数,无论何时调用,该函数总是返回相同的String消息:
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// Prints "hello, world"
尽管函数定义没有任何参数,但仍需要在函数名称后加上括号。 调用函数时,函数名称后还会有一对空括号。
2.2 多个参数的函数
函数可以具有多个输入参数,这些参数写在函数的括号内,并用逗号分隔。
此函数接受一个人的名字以及是否已经打过招呼,然后返回该人的问候语:
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参数值和标为hasgreeted的Bool参数值都传递给括号来调用greet(person:alreadyGreeted :)函数,并用逗号分隔。 请注意,此函数不同于前面部分中显示的greet(person :)函数。 尽管两个函数的名称都以greet开头,但是greet(person:alreadyGreeted :)函数采用两个参数,而greet(person :)函数仅采用一个参数。
2.3 没有返回值的函数
不需要函数来定义返回类型。 这是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 :)调用第一个函数,但忽略其返回值。 当调用第二个函数时,消息仍然由第一个函数打印,但是不使用返回的值。
注意
返回值可以忽略,但是说返回值的函数必须始终这样做。 在函数的底部结束时,具有定义了返回类型的函数不返回值,会导致编译时错误。
2.4 多个返回值的函数
您可以使用元组类型作为函数的返回类型,以将多个值作为一个复合返回值的一部分返回。
下面的示例定义了一个名为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"
请注意,在从函数返回元组时不需要命名元组的成员,因为它们的名称已被指定为函数返回类型的一部分。
2.5 可选的元组返回类型
如果要函数返回的元组类型,可能是“无值”,则可以使用可选的元组返回类型来反映整个元组可以为零。 您通过在元组类型的右括号后面加上问号来编写可选的元组返回类型,例如(Int,Int)? 或(字符串,整数,布尔)?
注意
可选的元组类型,例如(Int,Int)? 与包含可选类型(例如(Int ?, Int?))的元组不同。 对于可选的元组类型,整个元组是可选的,而不仅仅是元组中的每个单独的值。
上面的minMax(array :)函数返回一个包含两个Int值的元组。 但是,该函数不会对传递的数组执行任何安全检查。 如果array参数包含一个空数组,则如上所述,minMax(array :)函数将在尝试访问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"
2.6 隐式返回函数
如果函数的整个主体是单个表达式,则函数隐式返回该表达式。 例如,下面的两个函数具有相同的行为:
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关键字返回相同的问候消息。 您只写一个返回行的任何函数都可以忽略该返回。
就像在Shorthand Getter Declaration中所看到的那样,属性getter也可以使用隐式返回。
3 函数参数标签和参数名称
每个函数参数都具有参数标签和参数名称。 参数标签在调用函数时使用; 每个参数都写在函数调用中,并带有其参数标签。 参数名称在函数的实现中使用。 默认情况下,参数使用其参数名称作为其参数标签。
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)
所有参数必须具有唯一的名称。 尽管多个参数可能具有相同的参数标签,但独特的参数标签有助于使代码更具可读性。
3.1 指定参数标签
您在参数名称前写一个参数标签,并用空格隔开:
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."
变量标签的使用可以让函数以表达方式,类似于句子的方式被调用,同时还可以提供可读且意图明确的函数主体。
3.2 省略参数标签
如果您不希望为参数添加参数标签,请为该参数写下划线(_)而不是显式参数标签。
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)
如果参数具有参数标签,则在调用函数时必须对参数进行标签。
3.3 默认参数值
您可以为函数中的任何参数定义默认值,方法是在该参数的类型之后为该参数分配一个值。 如果定义了默认值,则可以在调用函数时忽略该参数。
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
将没有默认值的参数放在函数的参数列表的开头,在具有默认值的参数之前。 没有默认值的参数通常对函数的意义更重要-首先编写它们可以使您更容易识别正在调用相同的函数,而不管是否忽略了任何默认参数。
3.4 可变参数
可变参数接受零个或多个指定类型的值。 您可以使用可变参数来指定在调用函数时可以向该参数传递不同数量的输入值。 通过在参数的类型名称后插入三个句点(...)来编写可变参数。
传递给可变参数的值可在函数体内以适当类型的数组形式使用。 例如,在函数体内可以使用带有数字名称和Double ...类型的可变参数作为称为Numbers [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)
// 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
注意
一个函数最多可以具有一个可变参数。
3.5 in-out参数
函数参数默认为常量。 试图从函数主体内部更改函数参数的值会导致编译时错误。 这意味着您不能错误地更改参数的值。 如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为in-out参数。
您可以通过将in-out关键字放在参数类型的前面来编写in-out参数。 in-out参数具有一个值,该值传递给函数,由函数修改,然后传递回函数以替换原始值。 有关in-out参数的行为以及相关的编译器优化的详细讨论,请参见In-Out Parameters。
您只能将变量作为in-out参数的参数传递。 您不能将常量或文字值作为参数传递,因为无法修改常量和文字。 当您将"与"符号(&)用作变量的参数时,可以直接在其名称前放置一个&符号,以表明该变量可以被函数修改。
注意
in-out 参数不能有默认值,并且可变参数不能被用为in-out参数
这是一个名为swapTwoInts(: :)的函数的示例,该函数具有两个称为a和b的in-out整数参数:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoInts(: :)函数只是将b的值交换为a,然后将a的值交换为b。 该函数通过将a的值存储在一个称为临时A的临时常量中,然后将b的值赋给a,然后将临时A赋给b来执行此交换。
您可以使用两个Int类型的变量调用swapTwoInts(: :)函数来交换它们的值。 请注意,当将someInt和anotherInt的名称传递给swapTwoInts(: :)函数时,它们的前缀为&符:
ar 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的值。 in0out参数是函数在函数主体范围之外产生影响的另一种方法。
4 函数类型
每个函数都有特定的函数类型,由函数的参数类型和返回类型组成。
例如:
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
此示例定义了两个简单的数学函数,分别称为addTwoInts和multipleTwoInts。 这些函数各自采用两个Int值,并返回一个Int值,这是执行适当的数学运算的结果。
这两个函数的类型都是(Int,Int)-> Int。 可以理解为:
“具有两个参数(均为Int类型)并且返回值为Int类型的值的函数。”
这是另一个示例,针对没有参数或返回值的函数:
func printHelloWorld() {
print("hello, world")
}
此函数的类型为()-> Void,或“没有参数并返回Void的函数”。
4.1 使用函数类型
您可以像使用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
4.2 函数类型作为参数
您可以使用诸如(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(::_ :)以类型安全的方式将其某些功能移交给函数的调用者。
4.3 函数类型作为返回值
您可以将一个函数类型用作另一个函数的返回类型。 您可以通过在返回函数的返回箭头(->)之后立即编写完整的函数类型来执行此操作。
下一个示例定义了两个简单的函数,分别称为stepForward(_ :)和stepBackward(_ :)。 stepForward(_ :)函数返回的值比其输入值大一,而stepBackward(_ :)函数返回的值比其输入值小一。 这两个函数的类型都是(Int)-> Int:
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
这是一个名为choiceStepFunction(backward :)的函数,其返回类型为(Int)-> Int。 choiceStepFunction(backward :)函数基于布尔值向后返回stepForward(_ :)函数或stepBackward(_ :)函数:
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
现在,您可以使用choiceStepFunction(backward :)来获得向一个方向或另一个方向步进的函数:
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function
上面的示例确定是否需要正步或负步来逐步将名为currentValue的变量逐渐移近零。 currentValue的初始值为3,这意味着currentValue> 0返回true,从而导致selectStepFunction(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!
5 嵌套函数
到目前为止,您在本章中遇到的所有功能都是全局函数的示例,这些函数是在全局范围内定义的。 您还可以在其他函数体内定义函数,称为嵌套函数。
嵌套函数默认情况下对外界隐藏,但仍可以由其封闭函数调用和使用。 封闭函数还可以返回其嵌套函数之一,以允许该嵌套函数在另一个作用域中使用。
您可以重写上面的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!
总结
这一章节内容到这里就结束了,我们做个小结:
- 函数的定义和调用。
- 函数可以携带参数或者不带参数。
- 函数可以有返回值,作为该函数的类型,返回值写在->符号的后面;可以有多个返回值,如:返回值为元组;可选的元组返回类型在元组后面加?符号。
- 函数可以没有返回值,则该函数的类型为void类型,默认可以省略->符号和void类型。
- 函数参数标签,也就是函数参数名,在调用函数时,可以省略函数参数,做法是在声明函数时,参数名前加上“_”通配符,如:
func someFunction(_ firstParameterName: Int, secondParameterName: Int)
,调用时就可以someFunction(1, secondParameterName: 2)
。 - 声明函数且函数有多个参数时,可以给某个参数设置默认值;调用函数时就可以不传这个参数,如:
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12)
,调用时:someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6)
或someFunction(parameterWithoutDefault: 4)
。 - 可变参数接受零个或多个指定类型的值,如:
func arithmeticMean(_ numbers: Double...) -> Double
,调用时:arithmeticMean(1, 2, 3, 4, 5)
。 - 函数参数默认为常量。如果希望函数可以修改参数的值,并且希望这些更改在函数调用结束后仍然存在,可以将该参数定义为in-out参数,如:
func swapTwoInts(_ a: inout Int, _ b: inout Int)
,调用时,要声明两个var变量作为参数,并传入函数:swapTwoInts(&someInt, &anotherInt)
,用“&+变量”作为参数。 - 在swift里,函数可以用其返回值作为类型,所以函数可以作为另一个函数的返回值或者作为另一个函数的参数;具体代码请看文章里的内容。
- 函数内部可以嵌套另一个函数,嵌套函数默认情况下对外界隐藏,但仍可以由其封闭函数调用和使用。 封闭函数还可以返回其嵌套函数之一,以允许该嵌套函数在另一个作用域中使用。
最后一句话,喜欢的朋友可以给个👍吗,你的鼓励就是我的动力,嘿嘿~
参考文档:Swift - Functions