-
前言
函数是执行特定任务的自包含代码块。你为函数指定了一个标识其功能的名称,此名称用于“调用”函数以在需要时执行其任务。
Swift的统一函数语法足够灵活,可以表达从没有参数名称的简单C风格函数到具有每个参数的名称和参数标签的复杂Objective-C风格方法。
参数可以提供默认值以简化函数调用,并且可以作为输入输出参数传递,这些参数在函数完成执行后修改传递的变量。
Swift中的每个函数都有一个类型,由函数的参数类型和返回类型组成。
你可以像Swift中的任何其他类型一样使用此类型,
这使得将函数作为参数传递给其他函数以及从函数返回函数变得很容易。
函数也可以在其他函数中编写,以在嵌套函数范围内封装有用的功能。
-
正文
1. 定义和调用函数 (Defining and Calling Functions)
定义函数时,可以选择定义一个或多个命名的类型值,这个函数将其作为输入用的,称为参数。您还可以选择定义一种值,该函数在完成时将作为输出传回,称为返回类型。
每个函数都有一个函数名称,它描述了函数执行的任务。要使用函数,可以使用其名称“调用”该函数,并传递与函数参数类型匹配的输入值(称为参数)。必须始终以与函数参数列表相同的顺序提供函数的参数。
调用下面示例中的函数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!"
你
可以通过String
在person
参数标签后面传递一个值来调用该函数greet(person:)
,例如 greet(person: "Anna")。因为函数返回一个String值,可以在函数 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!"
2. 函数参数和返回值(Function Parameters and Return Values)
2.1 定义输入参数不需要函数。
这是一个没有输入参数的函数,无论何时调用它都会返回相同的消息String
:
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// Prints "hello, world"
函数定义在函数名称后仍需要括号,即使它不带任何参数。调用函数时,函数名后面还会有一对空括号。
2.2 具有多个参数的函数(Functions With Multiple Parameters)
函数可以有多个输入参数,这些参数写在函数的括号内,用逗号分隔。
此函数接受一个人的姓名以及他们是否已作为输入受到欢迎,并为该人返回适当的问候语:
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!"
你可以
通过在括号中传递一个String
标记person和一个
Bool标记
alreadyGreeted
参数值
来调用该函数greet(person:alreadyGreeted:)
,用逗号分隔。请注意,此功能greet(person:alreadyGreeted:)
与前面部分中greet(person:)
显示的功能不同。虽然两个函数都以名称开头greet
,但该函数有两个参数,但函数greet(person:)
只有一个。
可以有相同函数名,不同参数
2.3 没有返回值的函数 (Functions Without Return Values)
定义返回类型不需要函数。这是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 多个返回值的函数(Functions with Multiple Return Values)
您可以使用元组类型作为函数的返回类型,以将多个值作为一个复合返回值的一部分返回。
下面的示例定义了一个名为的函数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 可选的元组返回值类型(Optional Tuple Return Types)
如果要从函数返回的元组类型可能对整个元组具有“无值”,则可以使用可选的元组返回类型来反映整个元组的事实nil
。您可以通过在元组类型的右括号后面放置一个问号来编写可选的元组返回类型,例如:(Int, Int)?
或(String, Int, Bool)?
⚠️ 可选的元组类型,ex: (Int, Int)?
与包含可选类型的元组不同,ex: (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"
3. 函数的参数标签和参数名称(Function Argument Labels and Parameter Names)
每个函数参数都有参数标签和参数名称。调用函数时使用参数标签; 每个参数都在函数调用中写入,其前面带有参数标签。参数名称用于函数的实现。默认情况下,参数使用其参数名称作为其参数标签。
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 特定参数标签(Specifying Argument Labels)
你在参数名称前面编写参数标签,用空格分隔
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 省略参数标签(Omitting Argument Labels)
如果你不想要给参数一个参数标签,写一个下划线(_)代替明确的参数标签
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 默认参数值(Default Parameter Values)
你可以在函数里为任意参数定义一个默认值,通过在参数类型后给参数赋值。如果已经定义了一个默认值,在调用函数时,你可以省略这个参数。
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 变量参数(Variadic Parameters)一个变量参数接收0个或多个明确指定类型的值。当一个函数被调用时,你用一个变量参数去指定多个数量的输入值。通过在参数类型名后插入三个点字符(...)写变量参数.
传入一个变量函数的值是由可变的一个合适类型的数组出现在函数体内。例如:一个名字是number,类型是Double的可变参数,在函数体内作为一个常量数组,叫numbers,类型是[Double].
下面的例子计算arithmetic 意思是(平均值)对于一个任意长度的数字的list:
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 Parameters)
默认情况下,函数参数是常量。尝试从该函数体内更改函数参数的值会导致编译时错误。这意味着您无法错误地更改参数的值。如果希望函数修改参数的值,并且希望在函数调用结束后这些更改仍然存在,请将该参数定义为输入输出参数。
你可以通过将inout
关键字放在参数类型之前来编写输入输出参数。一个在出参数具有传递的值中,由函数修改的功能,并将该部分送回出的功能来代替原来的值。有关输入输出参数和相关编译器优化的行为的详细讨论,请参阅输入输出参数。
你只能将变量作为输入输出参数的参数传递。您不能传递常量或文字值作为参数,因为不能修改常量和文字。
当您将变量名称作为参数传递给输入输出参数时,可以将(&)
直接放在变量名称之前,以指示它可以被函数修改。
⚠️进出函数没有默认值,可变参数也不能被标记为进出参数
这有一个例子,一个被称作swapTwoInts(_:_:)的函数,它有2个进出整数型参数a和b.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
这个函数 swapTwoInts(_:_:)
简单地转换了b的值是a,a的值是b,这函数通过一个临时常量temporaryA
存a的值来执行转换,将b的值赋值给a,然后将temporaryA赋值给b
你可以传2个int变量,调用函数swapTwoInts(_:_:)
,去切换他们的值。⚠️当参数传进函数swapTwoInts(_:_:)时,参数someInt和anotherInt是有一个(&)前缀的
。
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(_:_:)
修改,尽管他们原始值是在函数外定义的。
⚠️进出参数和从一个函数获得返回值不同。上面这个函数的例子没有定义返回值和返回值类型,但是它依然修改了someInt和anotherInt的值。
输入输出参数是函数在其函数体范围之外产生效果的另一种方法。
4. 函数类型(Function Types)
每个函数都有一个特定的函数类型,由函数的参数类型和返回类型组成。例如:
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
4.1 使用函数类型(Using Function Types)
你可以像使用Swift中的任何其他类型一样使用函数类型。例如,您可以将常量或变量定义为函数类型,并为该变量分配适当的函数:
var mathFunction: (Int, Int) -> Int = addTwoInts
这可以理解为:“定义一个名为的变量mathFunction
,它的类型为'一个带两个Int
值的函数,并返回一个Int
值'。设置这个新变量来引用被调用的函数addTwoInts.”
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(_:_:_:)的函数,它有3个参数,第一个参数叫mathFunction,它的类型是
(Int, Int) -> Int.你可以传任何这个类型的函数作为第一个参数,第二个参数和第三个参数分别是a和b,他们的类型都是Int.他们都作为2个输入值为mathFunction提供。
当printMathResult(_:_:_:)函数被调用时,它传入addTwoInts(_:_:)
函数,3 和5 两个整数.它给这提供的函数一个 3和5的值,并打印结果8.
这个printMathResult(_:_:_:)的角色是打印一个叫
math function 的合适的类型的值的结果,它不关心函数实际的实现,它只关心这个函数的类型是不是正确.这能够让 printMathResult(_:_:_:)
以类型安全的方式将其一些功能传递给函数的调用者.
4.3 函数类型作为返回值类型
你可以用一个函数类型作为另一个函数的返回值,你可以在返回函数的返回剪头后马上写一个完整的函数类型.
下一个例子,定义了两个简单的函数stepForward(_:)和stepBackward(_:).这个
stepForward(_:)
function 返回值比输入值多1,这个stepBackward(_:)函数返回值比输入值少1,都有同一个函数类型(Int) -> Int
:
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
这里有一个叫chooseStepFunction(backward:)的函数,它的返回值类型是(Int) -> Int,这个函数返回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的
参数是否更接近0. currentValue有一个初始值3,它意味着currentValue > 0
的返回值是真,所以chooseStepFunction(backward:)
返回值是stepBackward(_:)
. 返回函数的参考是存一个叫moveNearerToZero的常量.
现在这个moveNearerToZero涉及到这个正确的函数,它可以被用于计算count 到0.
print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
4.4 嵌套函数(Nested Functions)
到目前为止,在本章中你遇到的所有函数都是全局函数的例子,它们被定义在一个全局的范围内.你可以在另一个函数体内定义函数,被叫做嵌套函数.
嵌套函数默认是对外隐藏的,但是它仍然可以被调用和使用通过他们的封闭函数.一个封闭函数也可以返回嵌套函数之一,允许嵌套函数在另一个范围内被调用。
你可以重写上面这个函数 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!
⚠️ 个人补充:函数类型和函数的嵌套,就是以后的闭包.