5-1 如何定义和使用函数
-
定义和调用函数
- 当你定义了一个函数的时候,你可以选择定义一个或者多个命名的分类的值作为函数的输入(所谓的形式参数),并且/或者定义函数完成后将要传回作为输出的值的类型(所谓它的返回类型)
- 每一个函数都有一个函数名,它描述函数执行的任务。要使用一个函数,你可以通过“调用” 函数的名字并且传入一个符合函数形式参数类型的输入值(所谓实际参数)来调用这个函数,给函数提供的实际参数的顺序必须符合函数的形式参数列表顺序。
func greet(person: String) -> String{
let greeting = "Hello, " + person + "!"
return greeting;
}
-
无形式参数的函数
- 函数没有要求必须输入一个参数,可以没有形式参数
- 函数的定义仍然需要在名字后边加一个圆括号,即使它不接受形式参数也得这样做,当函数被调用的时候也要在函数的名字后边加一个空的圆括号。
func sayHelloWorld() -> String{
return "hello, world"
}
print(sayHelloWorld())
-
多形式参数的函数
- 函数可以输入多个形式参数,可以写在函数后边的圆括号内,用逗号分隔
func greet(person: String, alreadyGreeted: Bool) -> String {
if alreadyGreeted{
return greetAgain(person: person)
}else{
return greet(person: person)
}
}
-
无返回值的函数
- 函数定义中没有要求必须有一个返回类型
- 不需要返回值,函数在定义的时候就没有包含返回箭头(->)或者返回类型
- 严格来讲,函数greet(person:)还是有一个返回值的,尽管没有定义返回值,没有定义返回类型的函数实际上会返回一个特殊的类型void,它其实是一个空的元组,作用相当于没有元素的元组,可以写作()。
func greet(person: String){
print("Hello, \(person)!")
}
greet(person: "Dave")
-
多返回值的函数
- 为了让函数返回多个值作为一个复合的返回值,你可以使用元组类型作为返回类型
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
}else if value > currentMax{
currentMax = value
}
}
return (currentMin, currentMax)
}
-
可选元组返回类型
- 如果元组在函数的返回类型中有可能“没有值”,你可以用一个可选元组返回类型来说明整个元组的可能是nil,写法是在可选元组类型的圆括号后边添加一个问号(?)例如(Int, Int)? 或者(String, Int, Bool)?
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
}else if value > currentMax{
currentMax = value
}
}
return (currentMin, currentMax)
}
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]){
print("min is \(bounds.min) and max is \(bounds.max)")
}
-
隐式返回的函数
- 如果整个函数体是一个单一表达式,那么函数隐式返回这个表达式
func greeting(for person: String) -> String{
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
5-2 函数的形式参数和返回值
-
实参标签和形参名
- 每一个函数的形式参数都包含实际参数标签和形式参数名,实际参数标签用在调用函数的时候;在调用函数的时候每一个实际参数前边都要写实际参数标签,形式参数名用在函数的实现当中。默认情况下,形式参数使用它们的形式参数名作为实际参数标签。
- 所有的形式参数必须有唯一的名字,尽管有可能多个形式参数拥有相同的实际参数标签,唯一的实际参数标签有助于让你的代码更加易读。
func someFunction(firstParameterName: Int, secondParameterName: Int) -> Int {
// In the function body, firstParameterName and secondParameterName refer to the argument values for the first and second parameters
}
someFunction(firstParameterName: 1, secondParameterName: 2)
-
指定实际参数标签
- 在提供形式参数名之前写实际参数标签,用空格分隔
- 如果你为一个形式参数提供了实际参数标签,那么这个实际参数就必须在调用函数的时候使用标签
- 实际参数标签的使用能够让函数的调用更加明确,更像是自然语句,同时还能提供更可读的函数体并更清晰地表达你的意图
func greet(person: String, from hometown: String) -> String{
return "Hello \(person)! Glad you could visit from \(hometown)"
}
print(greet(person: "Bill", from: "China"))
-
省略实际参数标签
*如果对于函数的形式参数不想使用实际参数标签的话,可以利用下划线( _ )来为这个形式参数代替显示的实际参数标签
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second patameters.
}
someFunction(1, secondParameterName: 2)
-
默认形式参数值
- 你可以通过在形式参数类型后给形式参数赋一个值来给函数的任意形式参数定义一个默认值
- 如果定义了默认值,你就可以在调用函数时候省略这个形式参数
func someFunction(parameterWithDefault: Int = 12) {
// In the function body, if no arguments are passed to the function
// call, the value of parameterWithDefault is 12
}
someFunction(parameterWithDefault: 6) // parameterWithDefault is 6
someFunction() // parameterWithDefault is 12
-
可变形式参数值
- 一个可变形式参数可以接受零或者多个特定类型的值,当调用函数的时候你可以利用可变形式参数来声明形式参数可以被传入值的数量是可变的。可以通过在形式参数的类型名称后边插入三个点符号(...)来书写可变形式参数。
- 传入到可变参数中的值在函数的主体中被当作是对应类型的数组
func arithmeticMean(_ numbers: Double...) -> Double {
var total : Double = 0
for number in numbers{
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
-
输入输出形式参数
- 可变形式参数只能在函数的内部做改变,如果你想函数能够修改一个形式参数的值,而且你想这些改变在函数结束之后依然生效,那么就需要将形式参数定义为输入输出形式参数
- 在形式参数定义开始的时候在前边添加一个
inout
关键字可以定一个输入输出形式参数。输入输出形式参数有一个能输入给函数的值,函数能对其进行修改,还能输出到函数外边替换原来的值- 你只能把变量作为输入输出形式参数的实际参数,在将变量作为实际参数传递给输入输出形式参数的时候,直接在它前边添加一个和符号(&)来明确可以被函数修改
- 输入输出形式参数不能有默认值,可变形式参数不能标记为
inout
func swapTwoInts(_ a: inout Int, _ b: inout Int){
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
5-3 函数类型和内嵌函数
-
函数类型
- 每一个函数都有一个特定的函数类型,它由形式参数类型,返回类型组成
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
----->
(Int, Int) -> Int
- 你可以像使用Swift中的其他类型一样使用函数类型。例如,你可以给一个常量或变量定义一个函数类型,并且为变量指定一个相应的函数。
func addTwoNumber(num: Int, num2: Int) -> Int{
return num + num2
}
var mathFunction: (Int, Int) -> Int = addTwoNumber
// 使用mathFunction这个变量直接调用,而不是addTwoNumber
print(mathFunction(2, 3))
-
函数类型作为形式参数类型
- 你可以利用使用一个函数的类型例如 (Int, Int) -> Int 作为其他函数的形式参数类型。这允许你预留函数的部分实现从而让函数的调用者在调用函数的时候使用
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
-
函数类型作为返回类型
- 你可以利用函数的类型作为另一个函数的返回类型。写法是在函数的返回箭头( -> )后立即写一个完整的函数类型
func stepForward(_ input: Int) -> Int{
return input + 1
}
func stepBackward(_ input: Int) -> Int{
return input + 1
}
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward;
}
// 测试
var currentVlaue = 3
let moveNearerToZero = chooseStepFunction(backwards: currentVlaue>0)
print("Counting to zero:")
// Counting to zero:
while currentVlaue != 0{
print("\(currentVlaue)...")
currentVlaue = moveNearerToZero(currentVlaue)
}
print("zero!")
-
内嵌函数
- 可以在函数的内部定义另外一个函数,这就是内嵌函数。
- 内嵌函数在默认情况下在外部是被隐藏起来的,但却仍然可以通过包裹它们的函数来调用它们,包裹的函数也可以返回它内部的一个内联函数来在另外的范围里使用
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int{ return input + 1 }
func stepBackward(input: Int) -> Int{ return input - 1}
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
while currentValue != 0{
print("\(currentValue)...")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
5-4 闭包和闭包表达式
-
闭包的概念
- 闭包是可以在你的代码中被传递和引用的功能性独立代码块
- 闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用,这也就是所谓的闭合并包裹那些常量和变量,因此被称为“闭包”,Swift 能够为你处理所有关于捕获的内存管理的操作
- 在函数章节中有介绍的全局和内嵌函数,实际上是特殊的闭包。闭包符合如下三种形式中的一种:
- 全局函数是一个有名字但不会捕获任何值但闭包
- 内嵌函数是一个有名字且能从其上层函数捕获值的闭包
- 闭包表达式是一个轻量级语法所写的可以捕获其上下文中常量或变量值的没有名字的闭包
-
闭包表达式
- 闭包表达式是一种在简短行内就能写完闭包的语法。
-
闭包表达式 - 从sorted函数说起
- Swift的标准库提供了一个叫做sorted(by:)的方法,会根据你提供的排序闭包将已知类型的数组的值进行排序。一旦它排序完成,sorted(by:)方法会返回与原数组类型大小完全相同的一个新数组,该数组的元素是已排序好的。原始数组不会被sorted(by:)方法修改:
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
print(reversedNames)
-
闭包表达式 - 语法
- 闭包表达式语法能够使用常量形式参数、变量形式参数和输入输出形式参数,但不能 提供默认值。可变形式参数也能使用,但需要在形式参数列表的最后面使用。元组也 可被用来作为形式参数和返回类型。
{
(parameters) -> (return type) in
statements
}
- 将之前backward(::)函数改为闭包表达版本
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
print(reversedNames)
- 从语境中推断类型
- 因排序闭包为实际参数来传递给函数,故Swift能推断它的形式参数类型和返回类型
- sorted(by:)方法期望它的形式参数是一个(String, String) -> Bool类型的函数。这意味着(String, String)和Bool类型不需要被写成闭包表达式定义中的一部分,因为所有的类型都能被推断,返回箭头( -> )和围绕在形式参数名周围的括号也能被省略
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })
print(reversedNames)
- 从单表达式闭包隐式返回
- 单表达式闭包能够通过从它们的声明中删掉
return
关键字来隐式返回它们单个表达式的结果
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })
print(reversedNames)
- 简写实际参数名
Swift
自动对行内闭包提供简写实际参数名,可以通过$0
,$1
,$2
等名字来引用闭包的实际参数值
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { $0 > $1 })
print(reversedNames)
- 运算符函数
Swift
的String
类型定义了关于大于号(>
)的特定字符串实现,让其作为一个有两 个String
类型形式参数的函数并返回一个Bool
类型的值。这正好与sorted(by:)
方 法的形式参数需要的函数相匹配。因此,你能简单地传递一个大于号,并且Swift
将 推断你想使用大于号特殊字符串函数实现
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: > )
print(reversedNames)
- 尾随闭包
- 如果你需要将一个很长的闭包表达式作为函数最后一个实际参数传递给函数,使用尾 随闭包将增强函数的可读性。尾随闭包是一个被书写在函数形式参数的括号外面(后面)的闭包表达式。
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted{ $0 > $1 }
print(reversedNames)
5-5 闭包捕获值
- 一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值
func makeIncrementer(forIncrement amount: Int) -> (() -> Int){
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
- 作为一种优化,如果一个值没有改变或者在闭包的外面,Swift可能会使用这个值的拷贝而不是捕获
- Swift也处理了变量的内存管理操作,当变量不再需要时会被释放
func makeIncrementer(forIncrement amount: Int) -> (() -> Int){
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer // () -> Int
}
let incrementByTen = makeIncrementer(forIncrement: 10) // () -> Int
incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30
incrementByTen() // 40
- 如果你建立了第二个
incrementer
,它将会有一个新的、独立的runningTotal
变量的引用
func makeIncrementer(forIncrement amount: Int) -> (() -> Int){
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer // () -> Int
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30
incrementByTen() // 40
// 新的、独立的runningTotal变量的引用
let incrementBySeven = makeIncrementer(forIncrement: 7) // () -> Int
incrementBySeven() // 7
// 调用的旧的
incrementByTen() // 50
- 闭包是引用类型
- 在
Swift
中,函数和闭包都是引用类型。- 无论你什么时候赋值一个函数或者闭包给常量或者变量,你实际上都是将常量和变量设置为对函数或者闭包对引用
func makeIncrementer(forIncrement amount: Int) -> (() -> Int){
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10) // () -> Int
incrementByTen() // 10
incrementByTen() // 20
let incrementBySeven = makeIncrementer(forIncrement: 7) // () -> Int
incrementBySeven() // 7
incrementByTen() // 30
let alsoIncrementByTen = incrementByTen // () -> Int
alsoIncrementByTen() // 40
- 闭包是引用类型
- 如果你分配了一个闭包给类实例的属性,并且闭包通过引用该实例或者它的成员来捕获实例,你将在闭包和实例间会产生循环引用。
5-6 逃逸闭包和自动闭包
- 逃逸闭包
- 当闭包作为一个实际参数传递给一个函数的时候,并且它会在函数返回之后调用,我 们就说这个闭包逃逸了。当你声明一个接受闭包作为形式参数的函数时,你可以在形 式参数前写
@escaping
来明确闭包是允许逃逸的。- 闭包可以逃逸的一种方法是被储存在定义于函数外的变量里。比如说,很多函数接收 闭包实际参数来作为启动异步任务的回调。函数在启动任务后返回,但是闭包要直到 任务完成——闭包需要逃逸,以便于稍后调用。
- 让闭包
@escaping
意味着你必须在闭包中显式地引用self
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void){
completionHandlers.append (completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void){
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x) // Prints "200"
completionHandlers.first?()
print(instance.x) // Prints "100"
- 自动闭包
- 自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包。它不 接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值。
- 这个语法的好处在于通过写普通表达式代替显式闭包而使你省略包围函数形式参数的括 号。
public func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)
let number = 3
assert(number > 3, "number 不大于3")
- 自动闭包-延迟处理
- 自动闭包允许你延迟处理,因此闭包内部的代码直到你调用它的时候才会运行。对于有副作用或者占用资源的代码来说很有用,因为它可以允许你控制代码何时才进行求值。
var customersInLine = ["Chris", "Alex", "Ewa", "Barry"]
print(customersInLine.count)
// Prints "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chirs!"
print(customersInLine.count)
// Prints "4"
- 当你传一个闭包作为实际参数到函数的时候,你会得到与延迟处理相同的行为。
func serve(customer customerProvider: () -> String){
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) })
// Prints "Now serving Alex!"
- 通过
@autoclosure
标志标记它的形式参数使用了自动闭包。现在你可以调用函数就 像它接收了一个String
实际参数而不是闭包。实际参数自动地转换为闭包,因为customerProvider
形式参数的类型被标记为@autoclosure
标记。
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String){
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa"
- 自动 + 逃逸
- 如果你想要自动闭包允许逃逸,就同时使用@autoclosure和@escaping标识
// customersInLine is ["Barry", "Daniella"]
// 闭包的数组
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"