欢迎关注微信公众号「Swift 花园」
书写函数
函数让我们可以重用代码。这句话的具体含义是,我们写一次函数,在多个地方使用。书写重复的代码,通常来说不是一种好的实践,而函数帮助我们避免重复代码。
用一个简单的例子开始吧。假设我们要为 app 的用户提供帮助信息,这个动作在 app 里的多个地方都会用到。因此,把打印帮助信息这个逻辑写成一个函数就是个好主意。
Swift 的函数以 func
关键字开始,然后跟着函数名,然后是一对圆括号,最后是一对花括号。当函数被运行时,花括号里的代码会被执行。
一个 printHelp ()
函数长这样:
1 | func printHelp() { |
然后我们用 printHelp ()
来运行它。
1 | printHelp () |
运行一个函数又被称为 调用 一个函数。
接收参数
函数在你调用它们的时候可以被定制,这使得它们的能力更加强大。 Swift 允许你发送值给函数,这些值可以在函数内部使用,从而改变函数的行为。其实之前我们已经见过函数的这个特性:我们把整数和字符串传递给 print ()
函数,就像下面这样。
1 | print("Hello, world!") |
这些被传入函数的值被称为 参数 。
为了使你的函数可以接收参数,你需要给参数起个名字,然后加一个冒号,最后再告诉 Swift 这个参数的数据类型。这些都是放在函数名之后的圆括号里面。
举个例子,我们可以写一个函数,打印任意数字的平方。
1 | func square(number: Int) { |
上面的代码告知 Swift 我们期望接收一个 Int
类型,并且名字叫 number
。这个名字不仅用于函数内部指代参数,也用于运行函数,就像这样:
1 | square (number: 8) |
返回值
函数不仅可以接收数据(通过参数),也可以返回数据。为了返回数据,在函数的参数列表之后写一个短横线加一个向右的尖括号,然后提供一个数据类型。这个语法告诉 Swift 函数将返回一个指定类型的数据。
在函数内,我们用 return
关键字来返回数据。这个时候函数会立即结束,并返回数据,函数内的其他代码都不再执行。
我们可以重写 square ()
函数,返回平方数而不是直接打印它:
1 | func square(number: Int) -> Int { |
现在我们可以通过运行这个函数,拿到返回值并且打印出来:
1 | let result = square (number: 8) |
如果你需要返回多个值,可以使用元组作为返回值的类型。
参数标签
前面的 square ()
函数:
1 | func square(number: Int) -> Int { |
参数名是 number
,我们通过在函数里使用 number
来引用这个参数,就像这样:
1 | let result = square (number: 8) |
Swift 允许我们给参数起两个名字:一个供调用时使用,一个在函数内部使用。书写的时候把两个名字都写上,用空格分隔。
举个例子:
1 | func sayHello(to name: String) { |
这里的参数名是 to name
,在外部调用时用 to
,而内部指代时用 name
。这种方式让参数在函数内部有一个合理的名称,同时在调用时阅读起来也是自然的。
1 | sayHello (to: "Taylor") |
省略参数标签
你可能已经注意到我们在使用 print ()
函数时并没有传入任何参数标签。我们会写作 print ("Hello")
,而不是 print (message:"Hello")
。
通过使用下划线 _
作为外部参数标签,你可以在自己的函数里实现一样的效果,就像这样:
1 | func greet(_ person: String) { |
这样写的话,调用 greet ()
函数时,你就不必传入参数标签了:
1 | greet ("Taylor") |
为了使代码阅读起来更自然,通常我们是需要给参数起一个外部标签名的。举个例子,如果我说设置闹钟 5( setAlarm (5)
)不写标签的话很难理解这代表什么意思:是要设置一个 5 点钟生效的闹钟呢?还是要激活第 5 个预先设好的闹钟?而像打印这件事,在打印后面直接跟上要打印的内容,本身就是一个清晰的表达,所以可以省略掉参数标签。
默认参数
print ()
函数打印文本到屏幕,并且不论你传什么内容给它,它都会在最后添加一个换行。所以多次调用 print ()
的话,那些文本是不会显示在同一行的。
但是你可以改变 print ()
函数的这个行为:你可以用其他符号,例如空格来取代换行。不过多数情况下,大家都想要换行,因此 print ()
有一个叫 terminator
的参数,它的默认值是换行符。
通过在参数后面加上一个 =
然后写上一个值,你可以为你自己的函数提供默认参数。 举个例子,我们写一个 greet ()
函数,默认友好问候:
1 | func greet(_ person: String, nicely: Bool = true) { |
现在 greet ()
函数就有两种调用方式了:
1 | greet ("Taylor") |
可变函数
有一些函数是 可变 的,可变是指函数可以接收任意多个同类型的参数。例如, print ()
函数实际上就是可变的:如果你传入多个参数,它们会被以空格相连打印在同一行。
1 | print("Haters", "gonna", "hate") |
你可以通过在参数类型之后添加 ...
,将一个参数声明成可变参数。因此,一个 Int
参数代表一个整数,而 Int...
则代表 0 个或者更多整数,理论上不限个数。
在函数内部, Swift 会将这些整数转成一个整数的数组,以方便你遍历它们。
让我们用 square ()
函数来尝试一下吧:
1 | func square(numbers: Int...) { |
现在我们可以用逗号分隔一组数字,把它们全部传入 square ()
函数:
1 | square (numbers: 1, 2, 3, 4, 5) |
书写会抛出错误的函数
有的时候函数会运行失败,因为不合理的输入或者函数内部的错误。 Swift 允许我们从函数中抛出错误。实现的方法是在返回值前写一个 throws
,然后在函数出错时使用 throw
关键字抛出错误。
首先我们需要定义一个 enum
,用于描述我们可能抛出的错误。这些错误必须基于 Swift 已经存在的 Error
类型。
1 | enum PasswordError: Error { |
现在我们来实现一个函数 checkPassword ()
,这个函数检测传入的密码是否合理,当密码过于简单时,我们抛出一个错误提醒用户。具体来说,当密码被设置成 “password” 时,执行 throw PasswordError.obvious
。
Swift 代码如下:
1 | func checkPassword(_ password: String) throws -> Bool { |
运行可能会抛出错误的函数
Swift 并不期望你在程序运行时遭遇错误,因此它不会让你直接运行可能抛出错误的函数。
你需要用到三个关键字来运行会抛出错误的函数: do
开启一段可能会遭遇问题的代码, try
放在每一个可能抛出错误的函数前面,最后的 catch
让你可以优雅地处理错误。
如果 do
语句块里有任何错误抛出,代码执行会直接跳到 catch
语句块。让我们用一个可以触发 checkPassword ()
抛出错误的密码来调用这个函数:
1 | do { |
当这段代码运行时,“你不能用这个密码。” 会被打印。但” 这个密码很棒!“则不会被打印。这个是由于错误的抛出导致那个打印无法被运行到。
inout 参数
所有传入 Swift 函数的参数默认都是 常量 ,所以你无法更改它们。假如你就是想要在函数内改变这些参数呢?可以用 inout
修饰它们,所有在函数内对它们做出的改变都会影响到它们在函数外的原始值。
举个例子,如果你想要让一个数翻倍。比如,直接改变那个数,而不是返回一个新的数。你可以像下面这样写:
1 | func doubleInPlace(number: inout Int) { |
为了使用这个可以修改参数的函数,首先要求传入的参数本身不能是常量,因为如果参数本来是常量,即使用 inout
修饰,也无法被修改。其次,在传入函数时,还要用一个 &
符号,放在参数名前面。它是参数以 inout
方式使用的显式标识。
在代码中,是这么书写的:
1 | var myNum = 10 |
总结
让我们来总结一下。
- 函数通过避免重复来帮助我们复用代码。
- 函数可以接收参数,你需要告诉 Swift 每个参数的类型。
- 函数可以返回值,同样需要指定返回值的类型。如果你想返回多个值,可以使用元组。
- 你可以给参数取不同的外部名和内部名,并且可以完全省略外部名。
- 参数可以有默认值,以便你用更少的代码指定常见的特定值。
- 可变参数可以接收零到多个特定的参数, Swift 会把它们转成数组。
- 函数可以抛出错误,但是调用这种函数的时候需要用
try
关键字并且用catch
来处理错误。 - 你可以用
inout
标记可以在函数内部改变的参数,不过通常来说最好还是返回一个新值。