首页 iOS.& Swift Books Swift学徒

5
功能 由MATT Galloway撰写

功能是许多编程语言的核心部分。简单地,函数允许您定义执行任务的代码块。然后,每当您的应用程序需要执行该任务时,都可以运行该函数而不是复制和粘贴到处都是相同的代码。

在本章中,您将学习如何编写自己的功能,并在第一手看到SWIFT使它们易于使用。

功能基础知识

想象一下,您有一个经常需要打印名称的应用程序。您可以编写一个函数来执行此操作:

func printMyName() {
  print("My name is Matt Galloway.")
}

上面的代码被称为a 函数声明. You define a function using the func keyword. After that comes the name of the function, followed by parentheses. You’ll learn more about the need for these parentheses in the next section.

括号中的开口支架后,后跟要在函数中运行的代码,然后是结束支架。使用函数定义,您可以使用它如下:

printMyName()

这打印了以下内容:

My name is Matt Galloway.

If you suspect that you’ve already used a function in previous chapters, you’re correct! print, which prints the text you give it to the console, is indeed a function. This leads nicely into the next section, in which you’ll learn how to pass data to a function and get data back in return.

功能参数

在前面的示例中,该功能只需打印出邮件。这很棒,但有时你想要 参数化 您的函数,它允许该功能根据通过其传递到它的数据而不同 参数.

例如,考虑以下功能:

func printMultipleOfFive(value: Int) {
  print("\(value) * 5 = \(value * 5)")
}
printMultipleOfFive(value: 10)

Here, you can see the definition of one parameter inside the parentheses after the function name, named value and of type Int. In any function, the parentheses contain what’s known as the 参数列表。即使参数列表为空,也会在调用函数时何时何时何时且在调用该函数时,需要这些括号。此功能将打印出五倍的五倍。在该示例中,您将函数称为 争论 10,所以该功能打印以下内容:

10 * 5 = 50

笔记:注意不要让术语“参数”和“参数”混淆。函数声明它 参数 在其参数列表中。当您调用函数时,您可以提供值 争论 对于函数的参数。

您可以更进一步地逐步迈出一步,使函数更一般。使用两个参数,该函数可以打印出任何两个值的倍数。

func printMultipleOf(multiplier: Int, andValue: Int) {
  print("\(multiplier) * \(andValue) = \(multiplier * andValue)")
}
printMultipleOf(multiplier: 4, andValue: 2)

There are now two parameters inside the parentheses after the function name: one named multiplier and the other named andValue, both of type Int.

Notice that you need to apply the labels in the parameter list to the arguments when you call a function. In the example above, you need to put multiplier: before the multiplier and andValue: before the value to be multiplied.

在SWIFT中,您应该尝试使您的函数调用像句子一样读取。在上面的示例中,您将阅读像这样的最后一行代码:

打印乘数4和值2的倍数

通过给出一个不同的参数,您可以使甚至更清晰 外部名称. For example, you can change the name of the andValue parameter:

func printMultipleOf(multiplier: Int, and value: Int) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}
printMultipleOf(multiplier: 4, and: 2)

You assign a different external name by writing it in front of the parameter name. In this example, the internal name of the parameter is now value while the external name (the argument label) in the function call is now and. You can read the new call as:

打印乘数4和2的倍数

下图解释了外部和内部名称来自函数声明:

这背后的想法是允许您在像句子中读取函数调用,但仍然在函数本身内具有表现力的名称。您可以写上面的功能,如下所示:

func printMultipleOf(multiplier: Int, and: Int)

This would have the same effect at the function call of being a nice readable sentence. However now the parameter inside the function is also called and. In a long function, it could get confusing to have such a generically named parameter.

If you want to have no external name at all, then you can employ the underscore _, as you’ve seen in previous chapters:

func printMultipleOf(_ multiplier: Int, and value: Int) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}
printMultipleOf(4, and: 2)

此更改使得在呼叫站点上更具可读。函数调用现在读取如下:

打印4和2的倍数

You could, if you so wished, take this even further and use _ for all parameters, like so:

func printMultipleOf(_ multiplier: Int, _ value: Int) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}
printMultipleOf(4, 2)

在此示例中,所有参数都没有外部名称。但这说明了如何明智地使用下划线。在这里,您的表达式仍然可以理解,但拍摄许多参数的更复杂功能可能会令人困惑和笨重,没有外部参数名称。想象一下,如果函数花了五个参数!

您还可以向参数提供默认值:

func printMultipleOf(_ multiplier: Int, _ value: Int = 1) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}
printMultipleOf(4)

The difference is the = 1 after the second parameter, which means that if no value is provided for the second parameter, it defaults to 1.

因此,此代码打印以下内容:

4 * 1 = 4

当您希望参数成为大部分时间的一个特定值时,它可能有用,并且当您调用该函数时,它将简化代码。

返回值

到目前为止你看到的所有功能都表现了一个简单的任务:打印出一些东西。函数也可以返回一个值。该函数的调用者可以将返回值分配给变量或常量,或直接在表达式中使用它。

使用返回值,您可以使用函数来转换数据。您只是通过参数进行数据,执行计算并返回结果。

以下是如何定义返回值的函数:

func multiply(_ number: Int, by multiplier: Int) -> Int {
  return number * multiplier
}
let result = multiply(4, by: 2)

To declare that a function returns a value, you add a -> followed by the type of the return value after the set of parentheses and before the opening brace. In this example, the function returns an Int.

Inside the function, you use a return statement to return the value. In this example, you return the product of the two parameters.

也可以通过使用元组来返回多个值:

func multiplyAndDivide(_ number: Int, by factor: Int)
                   -> (product: Int, quotient: Int) {
  return (number * factor, number / factor)
}
let results = multiplyAndDivide(4, by: 2)
let product = results.product
let quotient = results.quotient

此功能返回 两个都 the product and quotient of the two parameters: It returns a tuple containing two Int values with appropriate member value names.

通过元组返回多个值的能力是许多事情之一,使其很愉快地使用Swift。事实证明是一个方便的功能,因为很快就会看到。

You can make both of these functions simpler by removing the return, like so:

func multiply(_ number: Int, by multiplier: Int) -> Int {
  number * multiplier
}

func multiplyAndDivide(_ number: Int, by factor: Int)
                   -> (product: Int, quotient: Int) {
  (number * factor, number / factor)
}

你可以这样做,因为这个功能是一个 单一声明. If the function had more lines of code in it, then you wouldn’t be able to do this. The idea behind this feature is that in such simple functions it’s so obvious and the return gets in the way of readability.

For longer functions you need the return because you might make the function return in many different places.

高级参数处理

函数参数是常量默认情况下的,这意味着无法修改它们。

要说明这一点,请考虑以下代码:

func incrementAndPrint(_ value: Int) {
  value += 1
  print(value)
}

这导致错误:

Left side of mutating operator isn't mutable: 'value' is a 'let' constant

The parameter value is the equivalent of a constant declared with let. Therefore, when the function attempts to increment it, the compiler emits an error.

值得注意的是,Swift在将其传递到函数之前复制值,该行为已知为 通过价值.

笔记:通过价值和制作副本是您在本书中所见的所有类型的标准行为。您将看到另一种方式在第13章“类”中将进入功能。

通常,你想要这种行为。理想情况下,函数不会改变其参数。如果它确实,您无法确定参数的值,并且您可能会在代码中进行错误的假设,导致错误数据。

有时候你 想要让函数直接更改参数,这是一个已知的行为 复制 - 复制 或者 按价值结果调用。你这样做:

func incrementAndPrint(_ value: inout Int) {
  value += 1
  print(value)
}

在out 在参数类型指示应复制此参数之前,函数内使用的本地副本,并在函数返回时复制退出。

You need to make a slight tweak to the function call to complete this example. Add an ampersand (&) before the argument, which makes it clear at the call site that you are using copy-in copy-out:

var value = 5
incrementAndPrint(&value)
print(value)

现在该函数可以改变值但是它的意愿。

此示例将打印以下内容:

6

6

The function increments value and keeps its modified data after the function finishes. The value goes 到这个功能并回来了 出去 again, thus the keyword 在out.

在某些条件下,编译器可以简化复制复制 - 所谓的 通过逐个引用。参数值未复制到参数中。相反,参数将持有对原始值的内存的引用。此优化满足复制的所有要求,同时删除副本的需要。

过载

您是否介绍了在上一示例中使用相同的函数名称使用相同的函数名称?

func printMultipleOf(multiplier: Int, andValue: Int)
func printMultipleOf(multiplier: Int, and value: Int)
func printMultipleOf(_ multiplier: Int, and value: Int)
func printMultipleOf(_ multiplier: Int, _ value: Int)

这就是所谓的 过载 并允许您使用单个名称定义类似的函数。

但是,编译器仍然必须能够讲述这些功能之间的差异。每当您调用函数时,它应该始终清除您正在调用的功能。这通常通过参数列表中的差异来实现:

  • 不同数量的参数。
  • 不同的参数类型。
  • Different external parameter names, such as the case with printMultipleOf.

您还可以根据不同的返回类型过载函数名称,如下所示:

func getValue() -> Int {
  31
}

func getValue() -> String {
  "Matt Galloway"
}

Here, there are two functions called getValue(), which return different types–one an Int and the other a String.

使用这些是更复杂的。考虑以下:

let value = getValue()

How does Swift know which getValue() to call? The answer is, it doesn’t. And it will print the following error:

error: ambiguous use of 'getValue()'

There’s no way of knowing which one to call. It’s a chicken and egg situation. It’s unknown what type value is, so Swift doesn’t know which getValue() to call or what the return type of getValue() should be.

To fix this, you can declare what type you want value to be, like so:

let valueInt: Int = getValue()
let valueString: String = getValue()

This will correctly call the Int version of getValue() 在 the first instance, and the String version of getValue() 在 the second instance.

值得注意的是,应该与护理一起使用过载。仅限于相关和行为中相关的功能的过载。

当只有返回类型过载时,如上面的示例中,您松开了型推断,不建议使用。

迷你练习

  1. Write a function named printFullName that takes two strings called firstName and lastName. The function should print out the full name defined as firstName + " " + lastName. Use it to print out your own full name.
  2. Change the declaration of printFullName to have no external name for either parameter.
  3. Write a function named calculateFullName that returns the full name as a string. Use it to store your own full name in a constant.
  4. Change calculateFullName to return a tuple containing both the full name and the length of the name. You can find a string’s length by using the count property. Use this function to determine the length of your own full name.

用作变量

This may come as a surprise, but functions in Swift are simply another data type. You can assign them to variables and constants just as you can any other type of value, such as an Int 或者 a String.

要查看其工作原理,请考虑以下功能:

func add(_ a: Int, _ b: Int) -> Int {
  a + b
}

此函数需要两个参数并返回其值的总和。

您可以将此函数分配给变量,如下所示:

var function = add

Here, the name of the variable is 功能 and its type is inferred as (Int, Int) -> Int from the add 功能 you assign to it.

Notice how the function type (Int, Int) -> Int is written in the same way you write the parameter list and return type in a function declaration.

Here, the 功能 variable is of a function type that takes two Int 参数 and returns an Int.

Now you can use the 功能 variable in just the same way you’d use add, like so:

功能(4, 2)

这是返回6。

现在考虑以下代码:

func subtract(_ a: Int, _ b: Int) -> Int {
  a - b
}

Here, you declare another function that takes two Int 参数 and returns an Int. You can set the 功能 variable from before to your new subtract function, because the parameter list and return type of subtract is compatible with the type of the 功能 variable.

功能 = subtract
function(4, 2)

This time, the call to 功能 returns 2.

您可以将函数分配给变量的事实有用,因为它意味着您可以将功能传递给其他功能。这是一个在行动中的一个例子:

func printResult(_ function: (Int, Int) -> Int, _ a: Int, _ b: Int) {
  let result = function(a, b)
  print(result)
}
printResult(add, 4, 2)

printResult 需要三个参数:

  1. 功能 is of a function type that takes two Int 参数 and returns an Int, declared like so: (Int, Int) -> Int.
  2. a is of type Int.
  3. b is of type Int.

printResult calls the passed-in function, passing into it the two Int parameters. Then it prints the result to the console:

6

能够将功能传递给其他功能是非常有用的,它可以帮助您编写可重用的代码。您不仅可以传递周围的数据来操纵,但传递功能作为参数也意味着您可以灵活地执行代码的代码。

没有回报的土地

Some functions are never, ever, intended to return control to the caller. For example, think about a function that is designed to crash an application. Perhaps this sounds strange, so let me explain: if an application is about to work with corrupt data, it’s often best to crash rather than continue into an unknown and potentially dangerous state. The function fatalError("reason to terminate") is an example of a function like this. It prints the reason for the fatal error and then halts execution to prevent further damage.

非返回函数的另一个例子是处理事件循环的函数。事件循环处于从用户输入的每个现代应用程序的核心,并在屏幕上显示事项。然后,来自用户的事件循环服务请求,然后将这些事件传递给应用程序代码,这又导致屏幕上显示的信息。循环然后循环回并服务下一个事件。

These event loops are often started in an application by calling a function that is known to never return. Once you’re coding iOS or macOS apps, think back to this paragraph when you encounter UIApplicationMain 或者 NSApplicationMain.

Swift将为编译器抱怨,函数已知永远不会返回,如下所示:

func noReturn() -> Never {

}

Notice the special return type Never, indicating that this function will never return.

如果您写了此代码,您将收到以下错误:

Function with uninhabited return type 'Never' is missing call to another never-returning function on all paths

This is a rather long-winded way of saying that the function doesn’t call another “no return” function before it returns itself. When it reaches the end, the function returns to the place from which it was called, breaching the contract of the Never return type.

一个原油,但诚实的,实施不会回归的函数如下:

func infiniteLoop() -> Never {
  while true {
  }
}

您可能会想知道为什么会担心这种特殊的返回类型。它很有用,因为通过编译器知道该函数不会返回,它可以在生成代码调用该功能时进行某些优化。从本质上讲,调用该函数的代码不需要在函数调用后不需要打扰任何事情,因为它知道在终止应用程序之前,此函数永远不会结束。

写好功能

功能让你解决很多问题。最好的做 一个简单的任务 ,使它们更容易混合,匹配和模型进入更复杂的行为。

制作易于使用和理解的功能!为它们提供每次产生相同输出的定义良好的输入。您会发现它更容易理解和测试良好,干净,简单的功能。

评论你的功能

所有好软件开发人员都记录其代码。 :]

记录您的功能是确保当您稍后返回代码或与其他人共享时的重要一步,但可以在不必浏览代码的情况下理解。

幸运的是,Swift有一种简单的方法来记录与Xcode的代码完成和其他功能良好的功能。

它使用了defacto 做 xygen. 评论标准在SWIFT之外的许多其他语言使用。让我们来看看如何记录函数:

/// Calculates the average of three values
/// - Parameters:
///   - a: The first value.
///   - b: The second value.
///   - c: The third value.
/// - Returns: The average of the three values.
func calculateAverage(of a: Double, and b: Double, and c: Double) -> Double {
  let total = a + b + c
  let average = total / 3
  return average
}
calculateAverage(of: 1, and: 3, and: 5)

Instead of the usual double-/, you use triple-/ instead. Then the first line is the description of what the function does. Following that is a list of the parameters and finally, a description of the return value.

如果忘记文档评论的格式,只需突出显示函数并在Xcode中按“Option-Command- /”。 Xcode Editor将为您的注释模板插入您可以填写的注释模板。

创建此类代码文档时,您会发现该评论从通常的Monospace字体从Xcode中更改字体。整洁吗?好吧,是的,但还有更多。

首先,Xcode在代码完成出现时显示您的文档,如下所示:

此外,您可以按住选项键,然后单击函数名称,Xcode在方便的popover中显示您的文档,如下所示:

这两个都非常有用,您应该考虑记录所有功能,尤其是那些经常使用或复杂的功能。未来,你会稍后谢谢。 :]

挑战

在继续前进之前,以下是测试您对功能知识的一些挑战。最好尝试自己解决它们,但如果你被困,可以使用解决方案。这些随下载或在介绍中列出的印刷书的源代码链接中提供。

挑战1:带着步幅函数的循环

In the last chapter you wrote some for loops with countable ranges. Countable ranges are limited in that they must always be increasing by one. The Swift stride(from:to:by:) and stride(from:through:by:) functions let you loop much more flexibly.

例如,如果您想要从10到20循环到4,您可以写入:

for index in stride(from: 10, to: 22, by: 4) {
  print(index)
}
// prints 10, 14, 18

for index in stride(from: 10, through: 22, by: 4) {
  print(index)
}
// prints 10, 14, 18, and 22
  • 两个步幅函数过载有什么区别?
  • 写一个从10.0到(包括)9.0的循环,递减0.1。

挑战2:这是黄金时段

当我用编程语言熟悉自己时,我所做的第一件事之一是写一个函数来确定数字是否是素数。这是你的第二个挑战。

首先,写下以下功能:

func isNumberDivisible(_ number: Int, by divisor: Int) -> Bool

You’ll use this to determine if one number is divisible by another. It should return true when number is divisible by divisor.

暗示: You can use the modulo (%) operator to help you out here.

接下来,写主功能:

func isPrime(_ number: Int) -> Bool

This should return true if number is prime, and false otherwise. A number is prime if it’s only divisible by 1 and itself. You should loop through the numbers from 1 to the number and find the number’s divisors. If it has any divisors other than 1 and itself, then the number isn’t prime. You’ll need to use the isNumberDivisible(_:by:) 功能 you wrote earlier.

使用此功能来检查以下情况:

isPrime(6) // false
isPrime(13) // true
isPrime(8893) // true

暗示 1:小于0的数字不应被视为素数。在函数开始时检查此种情况,如果数字小于0,请提前返回。

暗示 2: Use a for loop to find divisors. If you start at two and end before the number itself, then as soon as you find a divisor, you can return false.

暗示 3:如果你想得到 真的 clever, you can simply loop from 2 until you reach the square root of number, rather than going all the way up to number itself. I’ll leave it as an exercise for you to figure out why. It may help to think of the number 16, whose square root is 4. The divisors of 16 are 1, 2, 4, 8 and 16.

挑战3:递归功能

在这一挑战中,您将看到函数调用时会发生什么 本身,一个叫做的行为 递归。这可能听起来不寻常,但它可能非常有用。

你要编写一个计算价值的函数 斐波纳契序列. Any value in the sequence is the sum of the previous two values. The sequence is defined such that the first two values equal 1. That is, fibonacci(1) = 1 and fibonacci(2) = 1.

使用以下声明写下您的函数:

func fibonacci(_ number: Int) -> Int

然后,通过使用以下数字执行它来验证您是否正确编写了功能:

fibonacci(1)  // = 1
fibonacci(2)  // = 1
fibonacci(3)  // = 2
fibonacci(4)  // = 3
fibonacci(5)  // = 5
fibonacci(10) // = 55

暗示 1: For values of number less than 0, you should return 0.

暗示 2: To start the sequence, hard-code a return value of 1 when number equals 1 or 2.

暗示 3: For any other value, you’ll need to return the sum of calling fibonacci with number - 1 and number - 2.

关键点

  • 你用A. 功能 要定义一项任务,您可以根据您的喜好执行多次,而无需多次编写代码。
  • 函数可以取零或更多 参数 并且可选地返回一个值。
  • 您可以将外部名称添加到函数参数以更改在函数调用中使用的标签,或者您可以使用下划线来表示无标签。
  • Parameters are passed as constants, unless you mark them as 在out, in which case they are copied-in and copied-out.
  • 函数可以具有与不同参数相同的名称。这称为超载。
  • 功能 can have a special Never return type to inform Swift that this function will never exit.
  • 您可以将函数分配给变量并将其传递给其他函数。
  • 努力创建清楚地命名的函数,并具有可重复输入和输出的一个作业。
  • Function documentation can be created by prefixing the function with a comment section using ///.

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC