Swift5.4 语言指南(八) 函数
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9727902.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
函数是执行特定任务的独立代码块。您为函数指定一个名称,该名称可以标识其功能,并且该名称用于“调用”该函数以在需要时执行其任务。
Swift的统一函数语法非常灵活,可以表达任何内容,从没有参数名称的简单C样式函数到具有每个参数名称和参数标签的复杂的Objective-C样式方法。参数可以提供默认值以简化函数调用,并且可以作为输入-输出参数传递,参数在函数完成执行后会修改传递的变量。
Swift中的每个函数都有一个类型,由该函数的参数类型和返回类型组成。您可以像Swift中的其他任何类型一样使用此类型,这使得将函数作为参数传递给其他函数以及从函数返回函数变得容易。也可以在其他函数中编写函数,以将有用的函数封装在嵌套函数范围内。
定义和调用函数
定义函数时,可以选择定义一个或多个该函数作为输入的命名的,键入的值,称为parameters。您还可以选择定义一种值的类型,该函数完成后将作为输出传回,称为函数的返回类型。
每个功能都有一个功能名称,该名称描述了该功能执行的任务。要使用函数,请使用函数名称“调用”该函数,然后将与函数参数类型匹配的输入值(称为arguments)传递给它。必须始终以与函数的参数列表相同的顺序提供函数的参数。
下例中的函数称为greet(person:)
,因为它就是这样做的—它以一个人的名字作为输入并返回该人的问候。为此,您需要定义一个输入参数(一个String
称为的值)person
和一个返回类型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!"
您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语句合并为一行:
- 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!"
您greet(person:alreadyGreeted:)
可以通过传递带有String
标签person
的Bool
参数值和带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:)
功能确实还是返回一个值,即使没有返回值的定义。没有定义的返回类型的函数将返回type的特殊值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?)
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"
隐式返回函数
如果函数的整个主体是单个表达式,则该函数隐式返回该表达式。例如,下面的两个函数具有相同的行为:
- 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
一行编写的任何函数都可以省略return
。
正如您将在Shorthand Getter Declaration中看到的那样,属性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...
作为函数的常量数组numbers
在type内可用,类型为type [Double]
。
下面的示例计算任意长度的数字列表的算术平均值(也称为average):
- 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
可变参数之后的第一个参数必须具有参数标签。参数标签使它明确地确定了将哪些参数传递给可变参数,以及将哪些参数传递给可变参数之后的参数。
一个函数可以具有多个可变参数。
进出参数
默认情况下,函数参数是常量。试图从函数主体内部更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。
您可以通过将inout
关键字放在参数类型的前面来编写输入/输出参数。的In-out参数有则传递值中,通过功能改进的功能,并传回了功能,以取代原来的值。有关输入输出参数的行为以及相关的编译器优化的详细讨论,请参见输入输出参数。
您只能将变量作为输入输出参数的参数传递。您不能传递常量或文字值作为参数,因为不能修改常量和文字。当您将一个与号(&
)作为变量传入in-out参数时,将它放在变量名的前面,以表明该变量可以被函数修改。
笔记
输入输出参数不能具有默认值,可变参数不能标记为inout
。
这是一个名为的函数的示例,该函数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
。
您可以swapTwoInts(_:_:)
使用两个类型的变量来调用函数Int
以交换它们的值。注意的名称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 {
- return a + b
- }
- func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
- return a * b
- }
本示例定义了两个简单的数学函数,分别称为addTwoInts
和multiplyTwoInts
。这些函数各自采用两个Int
值,并返回一个Int
值,这是执行适当的数学运算的结果。
这两个函数的类型均为。可以理解为:(Int, Int) -> Int
“具有两个参数(均为type Int
,并且返回type的值)的函数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
。
的作用printMathResult(_:_:_:)
是打印对适当类型的数学函数的调用结果。该函数的实现实际执行什么无关紧要,仅与该函数的类型正确无关。这使得可以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:)
stepForward(_:)
stepBackward(_:)
backward
- 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!