[极速Swift教程之五] 函数

536 阅读9分钟
更多内容,欢迎关注公众号:Swift花园
喜欢文章,不如来点赞关注吧

 

书写函数

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

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

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

一个 printHelp() 函数长这样:

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

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

printHelp()

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

  

接收参数

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

print("Hello, world!")

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

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

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

func square(number: Int) {
  print(number * number)
}

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

square(number: 8)

  

返回值

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

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

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

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

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

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

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

 

参数标签

前面的 square() 函数:

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

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

let result = square(number: 8)

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

举个例子:

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

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

sayHello(to: "Taylor")

 

省略参数标签

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

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

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

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

greet("Taylor")

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

 

默认参数

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

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

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

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

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

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

 

可变函数

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

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

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

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

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

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

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

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

 

书写会抛出错误的函数

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

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

enum PasswordError: Error {
  case obvious
}

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

Swift 代码如下:

func checkPassword(_ password: String) throws -> Bool {
  if password == "password" {
    throw PasswordError.obvious
  }

  return true
}

 

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

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

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

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

do {
  try checkPassword("password")
  print("这个密码很棒!")
} catch {
  print("你不能用这个密码。")
}

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

 

inout 参数

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

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

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

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

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

var myNum = 10 
doubleInPlace(number: &myNum)

 

总结

让我们来总结一下。

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

 

我的公众号

这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~