函数
Swift统一的函数语法足够灵活,可以用来表示任何函数,包括从最简单的没有参数名字的C风格函数,到复杂的带局部和外部参数名的Objective-C风格函数。参数可以提供默认值,以简化函数调用。参数也可以既当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值可以被修改。
在Swift中,每个函数都有一种类型,包括函数的参数值类型和返回值类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。
函数定义与调用
函数以func
前缀开头,用->
后面跟上返回类型。
func sayHello(personName: String) -> String {
return "Hello," + personName + "!"
}
print(sayHello("Tim"))
函数参数与返回值
无参函数
函数可以没有参数。尽管没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。
多参数函数
参数被包含在函数的括号之中,以逗号分隔。当调用超过一个参数的函数时,第一个参数后的参数根据其对应的参数名称标记。
无返回值函数
函数可以没有返回值。此时,函数的定义中没有->
和返回类型。实际上,没有定义返回类型的函数会返回特殊的值,叫 Void。它其实是一个空的元组(tuple),没有任何元素,可以写成()。被调用时,一个函数的返回值可以被忽略。但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,将导致编译错误(compile-time error)。
多重返回值函数
用元组(tuple)类型让多个值作为一个复合值从函数中返回,通过.
语法访问元组内各元素。元组的成员不需要在元组从函数中返回时命名,因为它们的名字已经在函数返回类型中指定了。
func minmax(arr: [Int]) -> (min: Int,max: Int) {
var min = arr[0]
var max = arr[0]
for data in arr[1..<arr.count] { // 从1开始
if data < min {
min = data
} else if data > max {
max = data
}
}
return (min,max)
}
let data = [1,2,34,1,11,-1]
let a = minmax(data)
print(a.min)
print(a.max)
可选元组返回类型
如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用可选的(Optional) 元组返回类型反映整个元组可以是nil的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组。注意:可选元组类型如(Int,Int)?
与元组包含可选类型如(Int?,Int?)
是不同的。可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。此时需要使用可选绑定来检查返回值!
// 为了安全地处理“空数组”问题,将返回值改为可选元组返回类型,并且当数组为空时返回nil。
func minmax(arr: [Int]) -> (min: Int,max: Int)? {
if arr.isEmpty {
return nil
}
var min = arr[0]
var max = arr[0]
for data in arr[1..<arr.count] { // 从1开始
if data < min {
min = data
} else if data > max {
max = data
}
}
return (min,max)
}
let data = [1,-1] // let data = [Int]()
let a = minmax(data)
print(a) // Optional((-1,34))
if let b = a {
print(b.min)
print(b.max)
} else {
print("array is empty")
}
函数参数名称
函数参数都有一个外部参数名(external parameter name)和一个局部参数名(local parameter name)。外部参数名用于在函数调用时标注传递给函数的参数,局部参数名在函数的实现内部使用。
一般情况下,第一个参数省略其外部参数名,第二个以及随后的参数使用其局部参数名作为外部参数名。所有参数必须有独一无二的局部参数名。尽管多个参数可以有相同的外部参数名,但不同的外部参数名能让你的代码更有可读性。
指定外部参数名
可以在局部参数名前指定外部参数名,中间以空格分隔。如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。
为每个参数指定外部参数名后,在调用时所有外部参数名都必须写出来。使用外部函数名可以使函数以一种更富有表达性的类似句子的方式调用,并使函数体意图清晰,更具可读性。
func sayHello(from sender: String,to receiver: String) {
print("\(sender) says to \(receiver),Hello!!!")
}
sayHello(from: "Tim",to: "Kate") // Tim says to Kate,Hello!!!
忽略外部参数名
如果不想为第二个及后续的参数设置外部参数名,用一个下划线(_
)代替一个明确的参数名。因为第一个参数默认忽略其外部参数名称,显式地写下划线是多余的。
func sayHello(sender: String,_ receiver: String) { // 使用_免去设置后面的外部参数名,而第一个也没必要写外部参数名,默认是忽略的
print("\(sender) says to \(receiver),Hello!!!")
}
sayHello("Tim","Kate") // Tim says to Kate,Hello!!!此时无需设置第2个之后的参数名!
默认参数值
可以在函数体中为每个参数定义默认值
(Deafult Values)。当默认值被定义后,调用这个函数时可以忽略这个参数。
将带有默认值的参数放在函数参数列表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。
func hello(name: String = "World") {
print("Hello,\(name)!!!")
}
hello("Tim")
hello()
可变参数
一个可变参数
(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(...
)的方式来定义可变参数。可变参数的传入值在函数体中变为此类型的一个数组。
一个函数最多只能有一个可变参数。如果函数有一个或多个带默认值的参数,而且还有一个可变参数,那么把可变参数放在参数表的最后。
func mean(numbers: Double...) -> Double {
if numbers.isEmpty {
return 0.0
}
var sum = 0.0
for number in numbers {
sum += number
}
return sum / Double(numbers.count)
}
print(mean()) // 不判断是否是空数组,结果就是nan
print(mean(1.1,2.2))
print(mean(2.2,44.3,33,3232,1111))
常量参数和变量参数
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。
但是有时候,如果函数中有传入参数的变量值副本将是很有用的。你可以通过指定一个或多个参数为变量参数,从而避免自己在函数中定义新的变量。变量参数不是常量,你可以在函数中把它当做新的可修改副本来使用。通过在参数名前加关键字var
来定义变量参数。对变量参数所进行的修改在函数调用结束后便消失了,并且对于函数体外是不可见的。变量参数仅仅存在于函数调用的生命周期中。
func f1(data: Int) {
// data = 1 // error: cannot assign to value: 'data' is a 'let' constant
}
func f2(var data: Int) -> Int {
++data
return data
}
let a = 10
print(f2(a)) // 11
此时,data就是仅函数内可见的局部变量,被传入的a初始化后,并在函数体中进行操作。
输入输出参数
前面的变量参数仅仅能在函数体内被更改。如果想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。
定义一个输入输出参数时,在参数定义前加inout
关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。
只能传递变量给输入输出参数。不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加&
符,表示这个值可以被函数修改。
输入输出参数不能有默认值,而且可变参数不能用inout
标记。如果你用inout
标记一个参数,这个参数不能被var
或者let
标记。
func swapInt(inout a: Int,inout _ b: Int) {
let c = a
a = b
b = c
}
var m = 10
var n = 20
swapInt(&m,&n)
print(m)
print(n)
输入输出参数和返回值是不一样的。上面的函数没有返回值却依然影响了参数。输入输出参数是函数对函数体外产生影响的另一种方式。
函数类型
每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。
func fun1(a: Int,_ b: Int) -> Int {
return a + b
}
func fun2() {
}
fun1(1,2)
fun2()
上面的两个函数的类型分别是:(Int,Int) -> Int
和() -> Void
。
使用函数类型
在Swift中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它。那么就可以使用这个常量或变量调用被赋值的函数。有相同匹配类型的不同函数可以被赋值给同一个变量,就像非函数类型的变量一样。像其他类型一样,当赋值一个函数给常量或变量时,你可以让 Swift 来推断其函数类型,而不用先声明其类型。
func fun1(a: Int,_ b: Int) -> Int {
return a + b
}
func fun2(a: Int,_ b: Int) -> Int {
return a - b
}
var a: (Int,Int) -> Int = fun1 // a和fun1具有相同的类型,这样的赋值是允许的
print(a) // (Function)
print(a(1,2)) // 调用被赋值的函数
a = fun2
print(a(1,2)) // 调用新的被赋值的函数
let b = fun2 // 直接赋值,不用先声明函数的类型
print(b(2,1))
函数类型作为参数类型
可以把函数类型作为另一个函数的参数类型。这样就可以将函数的一部分实现留给函数的调用者来提供。
func fun1(a: Int,_ b: Int) -> Int {
return a + b
}
func fun2(f: (Int,Int) -> Int,a: Int,b: Int) { // 文档中在a和b之前都加了_ print(f(a,b)) } fun2(fun1,a: 1,b: 2)
在上面的代码中,fun2(_:_:_:)
函数的作用就是输出某种类型的函数的调用结果。它不关心传入函数是如何实现的,它只关心这个传入的函数类型是正确的。这使得fun2(_:_:_:)
能以一种类型安全(type-safe)的方式将一部分功能转给调用者实现。
函数类型作为返回类型
func addOne(data: Int) -> Int {
return data + 1
}
func minusOne(data: Int) -> Int {
return data - 1
}
func fun(flag: Bool) -> (Int) -> Int {
if flag {
return addOne
} else {
return minusOne
}
}
var a = -2
print(fun(a < 0)(a)) // -1
嵌套函数
前面看到的函数都叫全局函数
(global functions),它们定义在全局域
中。你也可以把函数定义在别的函数体中,称作嵌套函数
(nested functions)。
默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(enclosing function)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。
func fun(flag: Bool) -> (Int) -> Int {
func addOne(data: Int) -> Int {
return data + 1
}
func minusOne(data: Int) -> Int {
return data - 1
}
if flag {
return addOne
} else {
return minusOne
}
}
var a = -2
print(fun(a < 0)(a)) // -1,一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用