Logo
Section 1 / 2

函数和方法

学习如何实现 Swift 中的方法和函数,并探索参数、实参和返回值。

检查点

函数和方法

学习如何实现 Swift 中的方法和函数,并探索参数、实参和返回值。

Swift 函数、方法和闭包是编写结构良好且高效代码的关键组成部分。它们还提供了一种组织程序并避免代码重复的方式。在本课程的这一部分,我们将学习如何在 Swift 中声明和使用函数、方法和闭包。

什么是函数?

函数是一段命名的代码块,可以被调用来执行特定任务。它可以接收用于执行任务的数据,并能够将结果返回给调用它的代码。例如,如果在 Swift 程序中需要执行某个算术计算,就可以将执行该计算的代码放入一个函数中。这个函数可以被设计为接受进行算术运算所需的数值(称为参数),并返回计算结果。在程序中的任何需要执行该计算的位置,只需调用该函数,将参数值作为实参传入,即可获得返回结果。

参数还是实参?

在讨论函数时,“参数”和“实参”这两个术语经常被混用。然而,它们之间存在细微差别:函数在被调用时能够接收的值被称为参数;而当函数实际被调用并传递这些值时,这些值则被称为实参。

什么是方法?

方法本质上是与特定类、结构体或枚举相关联的函数。例如,如果你在一个 Swift 类中声明一个函数(这将在接下来的“Swift 面向对象编程”课程中详细讲解),它就被视为一个方法。尽管本章后续内容主要讨论函数,但除非另有说明,这些规则和行为同样适用于方法。

如何声明 Swift 函数

Swift 函数使用以下语法进行声明:

1func <函数名称> (<参数名>: <参数类型>, 2 <参数名>: <参数类型>, ... ) -> <返回类型> { 3 // 函数代码 4}

函数名称、参数和返回类型组合起来被称为函数签名。对函数声明各部分的解释如下:

  • func:用于通知 Swift 编译器这是一个函数或方法的前缀关键字。
  • <函数名称>:分配给函数的名称。这是在应用程序代码中调用函数时所使用的名称。
  • <参数名>:在函数代码中引用参数的名称。
  • <参数类型>:对应参数的数据类型。
  • <返回类型>:函数返回结果的数据类型。如果函数不返回结果,则无需指定返回类型。
  • 函数代码:执行具体工作的函数内部代码。

例如,下面这个函数不接受任何参数,也不返回任何结果,仅用于显示一条消息:

1func sayHello() { 2 print("Hello") 3}

而下面这个示例函数则接受一个 Int 和一个 String 作为参数,并返回一个 String 类型的结果:

1func buildMessageFor(name: String, count: Int) -> String { 2 return("\(name), 你是最新的客户编号 \(count)") 3}

单表达式函数的隐式返回

在上一个例子中,使用了 return 语句来返回 buildMessageFor() 函数中的值。值得注意的是,如果函数只包含一个表达式(如本例所示),可以省略 return 语句。因此,buildMessageFor() 方法可以重写为如下形式:

1func buildMessageFor(name: String, count: Int) -> String { 2 "\(name), 你是最新的客户编号 \(count)" 3}

但只有当函数中只有一个表达式时,才能省略 return 语句。例如,以下代码无法编译,因为函数包含两个表达式,必须使用 return 语句:

1func buildMessageFor(name: String, count: Int) -> String { 2 let uppername = name.uppercased() 3 "\(uppername), 你是最新的客户编号 \(count)" // 无效表达式 4}

调用 Swift 函数

函数声明后,使用以下语法进行调用:

1<函数名称>(<实参1>, <实参2>, ... )

传递给函数的每个实参都必须与函数所配置接受的参数匹配。例如,要调用上面示例中名为 sayHello 且不接受参数、也不返回值的函数,可以这样写:

1func sayHello() { 2 print("Hello") 3} 4 5sayHello()

处理返回值

要调用名为 buildMessageFor 且接受两个参数并返回结果的函数,可以编写如下代码:

1func buildMessageFor(name: String, count: Int) -> String { 2 return("\(name), 你是最新的客户编号 \(count)") 3} 4 5let message = buildMessageFor(name: "John", count: 100) 6 7print(message)

在上述示例中,我们创建了一个名为 message 的新变量,并使用赋值操作符 (=) 将函数返回的结果存储进去。

在 Swift 开发中,有时函数或方法调用返回的结果不会被使用。在这种情况下,可以通过将其赋值给 _ 来丢弃返回值。例如:

1_ = buildMessageFor(name: "John", count: 100)

局部参数名与外部参数名

在前面的示例函数中,参数被赋予了可以在函数体内部引用的名称。这种名称被称为局部参数名。

除了局部名称外,函数参数还可以具有外部参数名。这些是调用函数时用来引用参数的名称。默认情况下,函数参数的局部名和外部名相同。例如,回顾之前对 buildMessageFor 方法的调用:

1let message = buildMessageFor(name: "John", count: 100)

如上声明,函数同时使用 “name” 和 “count” 作为局部名和外部名。

可以通过在局部参数名前加上下划线 _ 来移除默认的外部参数名,如下所示:

1func buildMessageFor(_ name: String, _ count: Int) -> String { 2 return("\(name), 你是最新的客户编号 \(count)") 3}

应用此更改后,函数现在可以这样调用:

1func buildMessageFor(_ name: String, _ count: Int) -> String { 2 return("\(name), 你是最新的客户编号 \(count)") 3} 4 5let message = buildMessageFor("John", 100) 6 7print(message)

或者,也可以通过在函数声明中先声明外部参数名,再写局部参数名来添加外部参数名。例如,以下代码中第一个和第二个参数的外部名分别设置为 username 和 usercount:

1func buildMessageFor(username name: String, usercount count: Int) -> String { 2 return("\(name), 你是最新的客户编号 \(count)") 3}

以这种方式声明后,调用函数时必须使用外部参数名:

1func buildMessageFor(username name: String, usercount count: Int) -> String { 2 return("\(name), 你是最新的客户编号 \(count)") 3} 4 5let message = buildMessageFor(username: "John", usercount: 100) 6 7print(message)

注意:无论调用函数时是否使用外部参数名传递实参,函数体内部仍使用局部参数名来引用参数。此外,使用外部参数名调用函数时,实参仍需按函数声明时的顺序排列。

声明默认函数参数

Swift 允许为参数指定默认值,当调用函数时未提供相应实参时,将使用该默认值。这只需在声明函数时为参数赋默认值即可。Swift 还会为带默认值的参数自动生成基于局部参数名的默认外部名(除非已手动指定),调用函数时必须使用该外部名。

为了演示默认参数的作用,将 buildMessageFor 函数修改为:若未传入客户姓名,则默认使用字符串“Customer”。这样,调用函数时可以不传入 name 实参:

1func buildMessageFor(_ name: String = "Customer", count: Int) -> String { 2 return("\(name), 你是最新的客户编号 \(count)") 3} 4 5let message = buildMessageFor(count: 100) 6print(message)

从函数返回多个结果

函数可以通过将多个结果包装在元组中来返回多个值。以下函数接受一个以英寸为单位的测量值作为参数,将其转换为码、厘米和米,并将这三个结果封装在一个元组中返回:

1func sizeConverter(_ length: Float) -> (yards: Float, centimeters: Float, meters: Float) { 2 let yards = length * 0.0277778 3 let centimeters = length * 2.54 4 let meters = length * 0.0254 5 return (yards, centimeters, meters) 6}

函数的返回类型表明其返回一个包含三个命名值的元组,分别为 yards、centimeters 和 meters,类型均为 Float:

1-> (yards: Float, centimeters: Float, meters: Float)

完成转换后,函数简单地构造一个元组实例并返回。

该函数的使用方式如下:

1func sizeConverter(_ length: Float) -> (yards: Float, centimeters: Float, meters: Float) { 2 let yards = length * 0.0277778 3 let centimeters = length * 2.54 4 let meters = length * 0.0254 5 return (yards, centimeters, meters) 6} 7 8let lengthTuple = sizeConverter(20) 9 10print("码 = \(lengthTuple.yards)") 11print("厘米 = \(lengthTuple.centimeters)") 12print("米 = \(lengthTuple.meters)")

接受可变数量的参数

在某些情况下,无法预先确定函数在调用时需要接受多少个参数。Swift 通过使用可变参数来处理这种情况。可变参数使用三个点号 (...) 声明,表示函数可以接受零个或多个指定类型的参数。在函数体内,这些参数以数组的形式可用。例如,以下函数接受任意数量的 String 类型参数,并将其输出到控制台:

1func displayStrings(_ strings: String...) { 2 for string in strings { 3 print(string) 4 } 5} 6 7displayStrings("one", "two", "three", "four")

参数作为变量

函数接收的所有参数默认都被视为常量。这防止了在函数代码中对这些参数值进行修改。如果需要在函数体中修改参数值,必须创建这些参数的副本(即“影子副本”)。例如,以下函数接收长度和宽度(单位为英寸),创建这两个值的影子变量,并将它们转换为厘米,然后计算并返回面积值:

1func calculateArea(length: Float, width: Float) -> Float { 2 var length = length 3 var width = width 4 length = length * 2.54 5 width = width * 2.54 6 return length * width 7} 8 9print(calculateArea(length: 10, width: 20))

使用输入输出参数

当变量作为参数传递给函数时,我们知道该参数在函数体中被视为常量。我们也知道,如果需要在函数体中修改参数值,必须创建影子副本,如上所述。由于这是副本,对变量所做的任何更改都不会自动反映在原始变量中。考虑以下代码:

1var myValue = 10 2 3func doubleValue(_ value: Int) -> Int { 4 var value = value 5 value += value 6 return value 7} 8 9print("调用函数前 myValue = \(myValue)") 10 11print("doubleValue 调用返回 \(doubleValue(myValue))") 12 13print("调用函数后 myValue = \(myValue)")

后续代码行显示了函数调用前后 myValue 变量的值。

显然,函数并未改变原始的 myValue 变量。这是预期的结果,因为数学运算是在变量的副本上进行的,而不是在 myValue 本身上。

为了使对参数的修改在函数返回后仍然有效,必须在函数声明中将参数标记为输入输出参数。为了演示这一点,我们需要修改 doubleValue 函数,加入 inout 关键字,并移除影子副本的创建。最后,在调用函数时,输入输出参数必须用 & 符号前缀:

1var myValue = 10 2 3func doubleValue(_ value: inout Int) -> Int { 4 value += value 5 return value 6} 7 8print("调用函数前 myValue = \(myValue)") 9 10print("doubleValue 调用返回 \(doubleValue(&myValue))") 11 12print("调用函数后 myValue = \(myValue)")

函数作为参数

Swift 中一个有趣的特性是,函数可以被视为数据类型。完全可以将函数赋值给常量或变量,如下所示:

1func inchesToFeet(_ inches: Float) -> Float { 2 return inches * 0.0833333 3} 4 5let toFeet = inchesToFeet

上述代码声明了一个名为 inchesToFeet 的新函数,并将其赋值给一个名为 toFeet 的常量。完成赋值后,可以通过常量名而非原函数名来调用该函数:

1func inchesToFeet(_ inches: Float) -> Float { 2 return inches * 0.0833333 3} 4 5let toFeet = inchesToFeet 6let result = toFeet(10) 7 8print("结果 = \(result)")

表面上看,这似乎不是一个特别有吸引力的功能。因为我们原本就可以直接调用函数,而不必将其赋值给常量或变量,因此似乎没有明显收益。

然而,当考虑到赋值给常量或变量的函数具备其他数据类型的能力时,这一特性的潜力才真正显现出来。特别是,函数现在可以作为参数传递给其他函数,甚至可以作为函数的返回值。

在深入探讨“将一个函数插入另一个函数”的能力之前,首先需要理解函数数据类型的概念。函数的数据类型由其接受的参数和返回的结果类型共同决定。在上述示例中,由于函数接受一个浮点数参数并返回一个浮点数结果,其数据类型符合以下格式:

1(Float) -> Float

另一方面,一个接受 Int 和 Double 作为参数并返回 String 结果的函数,其数据类型为:

1(Int, Double) -> String

为了接收函数作为参数,接收函数只需声明其能接受的函数数据类型。

为了举例说明,我们先声明两个单位转换函数,并将它们赋值给常量:

1func inchesToFeet(_ inches: Float) -> Float { 2 return inches * 0.0833333 3} 4 5func inchesToYards(_ inches: Float) -> Float { 6 return inches * 0.0277778 7} 8 9let toFeet = inchesToFeet 10let toYards = inchesToYards

接下来,我们需要一个额外的函数,其作用是执行单位转换并将结果打印到控制台。该函数应尽可能通用,能够执行多种不同的单位转换。为了演示函数作为参数的功能,这个新函数将接受一个与 inchesToFeet 和 inchesToYards 函数数据类型匹配的函数类型,以及一个待转换的值。由于这些函数的数据类型等同于 (Float) -> Float,我们的通用函数可以这样编写:

1func outputConversion(_ converterFunc: (Float) -> Float, value: Float) { 2 let result = converterFunc(value) 3 print("转换结果为 \(result)") 4}

当调用 outputConversion 函数时,必须传入一个与声明的数据类型匹配的函数。该函数将被调用来执行转换,结果将在控制台中显示。这意味着,通过“插入”适当的转换函数作为参数,可以使用同一个函数将英寸转换为英尺或码。例如:

1func inchesToFeet(_ inches: Float) -> Float { 2 return inches * 0.0833333 3} 4 5func inchesToYards(_ inches: Float) -> Float { 6 return inches * 0.0277778 7} 8 9func outputConversion(_ converterFunc: (Float) -> Float, value: Float) { 10 let result = converterFunc(value) 11 print("转换结果为 \(result)") 12} 13 14let toFeet = inchesToFeet 15let toYards = inchesToYards 16 17outputConversion(toYards, value: 10) // 转换为码 18outputConversion(toFeet, value: 10) // 转换为英尺

函数也可以作为数据类型返回,只需将函数类型声明为返回类型即可。以下函数根据布尔参数的值,返回 toFeet 或 toYards 函数类型(即接受并返回 Float 值的函数):

1func decideFunction(_ feet: Bool) -> (Float) -> Float { 2 if feet { 3 return toFeet 4 } else { 5 return toYards 6 } 7}

课程小结

  • 函数是一段可从代码其他位置调用的命名代码块。
  • 方法是与特定类、结构体或枚举相关联的函数。
  • 函数可以作为参数传递并返回结果。
  • 包含单条语句的函数可以不使用 return 语句直接返回结果。
  • 局部参数名用于在函数内部访问传入的参数。
  • 外部参数名用于调用函数时引用参数。
  • 函数可以配置为接受可变数量的参数。
  • 输入输出参数用于按引用传递参数,而非按值传递。
  • 函数本身可以作为参数传递给其他函数。

测验

本课内容丰富。请完成以下测验,确保你对 Swift 函数和方法有充分理解。

练习

完成以下练习,以增强你在 Swift 中使用函数和方法的信心。

练习 1

修正以下函数声明中的语法警告:

练习

1func sayHello(name: String) -> Bool { 2 print("Hello, \(name)") 3 return true 4} 5 6sayHello(name: "John")

解答

1func sayHello(name: String) -> Bool { 2 print("Hello, \(name)") 3 return true 4} 5 6_ = sayHello(name: "John")

练习 2

修改以下函数和代码,使用 inout 参数:

练习

1var value = 68 2 3func halfValue(value: Int) { 4 var value = value 5 value /= 2 6} 7 8halfValue(value: value) 9print("result = \(value)")

解答

1var value = 68 2 3func halfValue(value: inout Int) { 4 value /= 2 5} 6 7halfValue(value: &value) 8print("result = \(value)")

练习 3

修改 addNumbers 函数,使其能接受任意数量的参数:

练习

1func addNumbers(_ number1: Int, _ number2: Int) -> Int { 2 return number1 + number2 3} 4 5let result = addNumbers(10, 20) 6print("Result = \(result)")

解答

1func addNumbers(_ numbers: Int...) -> Int { 2 var total = 0 3 for number in numbers { 4 total += number 5 } 6 return total 7} 8 9let result = addNumbers(10, 20, 40, 50) 10print("Result = \(result)")

Quiz

多选题

请选择所有适用项。以下哪些是有效的 Swift 函数示例?

1 / 3

Swift 控制流

闭包表达式