极速 Swift 教程之五 | 函数

欢迎关注微信公众号「Swift 花园」

书写函数

函数让我们可以重用代码。这句话的具体含义是,我们写一次函数,在多个地方使用。书写重复的代码,通常来说不是一种好的实践,而函数帮助我们避免重复代码。

用一个简单的例子开始吧。假设我们要为 app 的用户提供帮助信息,这个动作在 app 里的多个地方都会用到。因此,把打印帮助信息这个逻辑写成一个函数就是个好主意。

Swift 的函数以 func 关键字开始,然后跟着函数名,然后是一对圆括号,最后是一对花括号。当函数被运行时,花括号里的代码会被执行。

一个 printHelp () 函数长这样:

1
2
3
4
5
6
7
8
func printHelp() {
let message = """
欢迎来到我的 App!
在一个图片的目录下运行这个 App,
它会把所有的图片缩放成缩略图。
"""
print(message)
}

然后我们用 printHelp () 来运行它。

1
printHelp ()

运行一个函数又被称为 调用 一个函数。


接收参数

函数在你调用它们的时候可以被定制,这使得它们的能力更加强大。 Swift 允许你发送值给函数,这些值可以在函数内部使用,从而改变函数的行为。其实之前我们已经见过函数的这个特性:我们把整数和字符串传递给 print () 函数,就像下面这样。

1
print("Hello, world!")

这些被传入函数的值被称为 参数

为了使你的函数可以接收参数,你需要给参数起个名字,然后加一个冒号,最后再告诉 Swift 这个参数的数据类型。这些都是放在函数名之后的圆括号里面。

举个例子,我们可以写一个函数,打印任意数字的平方。

1
2
3
func square(number: Int) {
print(number * number)
}

上面的代码告知 Swift 我们期望接收一个 Int 类型,并且名字叫 number 。这个名字不仅用于函数内部指代参数,也用于运行函数,就像这样:

1
square (number: 8)

返回值

函数不仅可以接收数据(通过参数),也可以返回数据。为了返回数据,在函数的参数列表之后写一个短横线加一个向右的尖括号,然后提供一个数据类型。这个语法告诉 Swift 函数将返回一个指定类型的数据。

在函数内,我们用 return 关键字来返回数据。这个时候函数会立即结束,并返回数据,函数内的其他代码都不再执行。

我们可以重写 square () 函数,返回平方数而不是直接打印它:

1
2
3
func square(number: Int) -> Int {
return number * number
}

现在我们可以通过运行这个函数,拿到返回值并且打印出来:

1
2
let result = square (number: 8)
print(result)

如果你需要返回多个值,可以使用元组作为返回值的类型。


参数标签

前面的 square () 函数:

1
2
3
func square(number: Int) -> Int {
return number * number
}

参数名是 number ,我们通过在函数里使用 number 来引用这个参数,就像这样:

1
let result = square (number: 8)

Swift 允许我们给参数起两个名字:一个供调用时使用,一个在函数内部使用。书写的时候把两个名字都写上,用空格分隔。

举个例子:

1
2
3
func sayHello(to name: String) {
print("Hello, \(name)!")
}

这里的参数名是 to name ,在外部调用时用 to ,而内部指代时用 name 。这种方式让参数在函数内部有一个合理的名称,同时在调用时阅读起来也是自然的。

1
sayHello (to: "Taylor")

省略参数标签

你可能已经注意到我们在使用 print () 函数时并没有传入任何参数标签。我们会写作 print ("Hello") ,而不是 print (message:"Hello")

通过使用下划线 _ 作为外部参数标签,你可以在自己的函数里实现一样的效果,就像这样:

1
2
3
func greet(_ person: String) {
print("Hello, \(person)!")
}

这样写的话,调用 greet () 函数时,你就不必传入参数标签了:

1
greet ("Taylor")

为了使代码阅读起来更自然,通常我们是需要给参数起一个外部标签名的。举个例子,如果我说设置闹钟 5( setAlarm (5) )不写标签的话很难理解这代表什么意思:是要设置一个 5 点钟生效的闹钟呢?还是要激活第 5 个预先设好的闹钟?而像打印这件事,在打印后面直接跟上要打印的内容,本身就是一个清晰的表达,所以可以省略掉参数标签。


默认参数

print () 函数打印文本到屏幕,并且不论你传什么内容给它,它都会在最后添加一个换行。所以多次调用 print () 的话,那些文本是不会显示在同一行的。

但是你可以改变 print () 函数的这个行为:你可以用其他符号,例如空格来取代换行。不过多数情况下,大家都想要换行,因此 print () 有一个叫 terminator 的参数,它的默认值是换行符。

通过在参数后面加上一个 = 然后写上一个值,你可以为你自己的函数提供默认参数。 举个例子,我们写一个 greet () 函数,默认友好问候:

1
2
3
4
5
6
7
func greet(_ person: String, nicely: Bool = true) {
if nicely == true {
print(" 你好, \(person)!")
} else {
print(" 不是吧,又是 \(person) 你小子。")
}
}

现在 greet () 函数就有两种调用方式了:

1
2
greet ("Taylor")
greet ("Taylor", nicely: false)

可变函数

有一些函数是 可变 的,可变是指函数可以接收任意多个同类型的参数。例如, print () 函数实际上就是可变的:如果你传入多个参数,它们会被以空格相连打印在同一行。

1
print("Haters", "gonna", "hate")

你可以通过在参数类型之后添加 ... ,将一个参数声明成可变参数。因此,一个 Int 参数代表一个整数,而 Int... 则代表 0 个或者更多整数,理论上不限个数。

在函数内部, Swift 会将这些整数转成一个整数的数组,以方便你遍历它们。

让我们用 square () 函数来尝试一下吧:

1
2
3
4
5
func square(numbers: Int...) {
for number in numbers {
print("\(number) 的平方等于 \(number * number)")
}
}

现在我们可以用逗号分隔一组数字,把它们全部传入 square () 函数:

1
square (numbers: 1, 2, 3, 4, 5)

书写会抛出错误的函数

有的时候函数会运行失败,因为不合理的输入或者函数内部的错误。 Swift 允许我们从函数中抛出错误。实现的方法是在返回值前写一个 throws ,然后在函数出错时使用 throw 关键字抛出错误。

首先我们需要定义一个 enum ,用于描述我们可能抛出的错误。这些错误必须基于 Swift 已经存在的 Error 类型。

1
2
3
enum PasswordError: Error {
case obvious
}

现在我们来实现一个函数 checkPassword () ,这个函数检测传入的密码是否合理,当密码过于简单时,我们抛出一个错误提醒用户。具体来说,当密码被设置成 “password” 时,执行 throw PasswordError.obvious

Swift 代码如下:

1
2
3
4
5
6
7
func checkPassword(_ password: String) throws -> Bool {
if password == "password" {
throw PasswordError.obvious
}

return true
}

运行可能会抛出错误的函数

Swift 并不期望你在程序运行时遭遇错误,因此它不会让你直接运行可能抛出错误的函数。

你需要用到三个关键字来运行会抛出错误的函数: do 开启一段可能会遭遇问题的代码, try 放在每一个可能抛出错误的函数前面,最后的 catch 让你可以优雅地处理错误。

如果 do 语句块里有任何错误抛出,代码执行会直接跳到 catch 语句块。让我们用一个可以触发 checkPassword () 抛出错误的密码来调用这个函数:

1
2
3
4
5
6
do {
try checkPassword ("password")
print(" 这个密码很棒!")
} catch {
print(" 你不能用这个密码。")
}

当这段代码运行时,“你不能用这个密码。” 会被打印。但” 这个密码很棒!“则不会被打印。这个是由于错误的抛出导致那个打印无法被运行到。


inout 参数

所有传入 Swift 函数的参数默认都是 常量 ,所以你无法更改它们。假如你就是想要在函数内改变这些参数呢?可以用 inout 修饰它们,所有在函数内对它们做出的改变都会影响到它们在函数外的原始值。

举个例子,如果你想要让一个数翻倍。比如,直接改变那个数,而不是返回一个新的数。你可以像下面这样写:

1
2
3
func doubleInPlace(number: inout Int) {
number *= 2
}

为了使用这个可以修改参数的函数,首先要求传入的参数本身不能是常量,因为如果参数本来是常量,即使用 inout 修饰,也无法被修改。其次,在传入函数时,还要用一个 & 符号,放在参数名前面。它是参数以 inout 方式使用的显式标识。

在代码中,是这么书写的:

1
2
var myNum = 10 
doubleInPlace (number: &myNum)

总结

让我们来总结一下。

  1. 函数通过避免重复来帮助我们复用代码。
  2. 函数可以接收参数,你需要告诉 Swift 每个参数的类型。
  3. 函数可以返回值,同样需要指定返回值的类型。如果你想返回多个值,可以使用元组。
  4. 你可以给参数取不同的外部名和内部名,并且可以完全省略外部名。
  5. 参数可以有默认值,以便你用更少的代码指定常见的特定值。
  6. 可变参数可以接收零到多个特定的参数, Swift 会把它们转成数组。
  7. 函数可以抛出错误,但是调用这种函数的时候需要用 try 关键字并且用 catch 来处理错误。
  8. 你可以用 inout 标记可以在函数内部改变的参数,不过通常来说最好还是返回一个新值。

Linkedin
Plus
Share
Class
Send
Send
Pin