swift语法基础(四)

函数

swift统一的函数语法非常灵活,可以用来表示任何函数,包括最简单的没有参数名字的c风格函数,到复杂的带局部外部参数名的OC风格参数,参数可以听过默认值,以简化函数调用。参数也可以及当作传入参数,也当作传出参数。也就是说,一旦函数执行,传入的参数值会被修改。

swift中每一个函数都有一个由函数的参数类型和返回值类型组成的类型。可以把函数类型当作任何其他变量类型处理。这样可以简单的把函数当作别的函数的参数。也可以从其他函数中返回函数。函数定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现封装。

函数的定义和调用

当定义一个函数时,可以定义一个或多个有名字和类型的值,作为函数的输入,称为参数,也可以定义某种类型的值作为函数执行结束的输出作为返回类型。

每个函数有一个函数名,用来描述函数执行的任务,使用函数时,用函数名调用。并传给它匹配的输入值(实参),函数的实参必须与函数参数表里参数顺序一致。

下面是一个 greet函数例子:

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
}
print(greetAgain(person: "Anna"))
// 打印“Hello again, Anna!”

所有这些信息汇总起来称为函数定义,以func作为前缀。 -> 后跟返回类型。

函数参数与返回值

  • 无参函数

    函数可以没有参数,这个函数就是一个无参数函数。但是函数名后面还是需要圆括号。

  • 多参数函数

函数可以有多种输入参数,这些参数被包含在函数的括号中,用逗号分隔。

func greet(person: String, alreadyGreeted: Bool) -> String {
if alreadyGreeted {
    return greetAgain(person: person)
} else {
    return greet(person: person)
}
}
print(greet(person: "Tim", alreadyGreeted: true))
// 打印“Hello again, Tim!”
  • 无返回值函数

函数可以没有返回值,如下:

func greet(person: String) {
    print("Hello, \(person)!")
}
greet(person: "Dave")
// 打印“Hello, 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)
}

该函数返回一个包含两个Int的元组,这些值被标记为min和max。

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
// 打印“min is -6 and max is 109”

需要注意的是元组成员不需要在元组从函数中返回时命名。因为名字已经在返回类型中指定了。

  • 可选元组返回类型

如果函数返回的元组类型可能整个元组都没有值,可以使用可选的元组作为返回类型反映元组是nil。可以通过在元组类型的右括号后放置一个问号来定义一个可选元组。例如:(Int,Int)?或者 (String,Bool,Int)?

前面minmax(array:)函数返回一个包含两个Int值的元组,但是函数不会对传入的数组执行任何安全检查,如果array是一个空数组,在执行时会触发错误。所以为了解决这个问题,函数改写为使用可选元组返回类型,当数组为空时返回nil:

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)
}
  • 隐式返回的函数

如果一个函数的整个函数体是单行表达式,这个函数可以隐式的返回这个表达式。

函数标签和参数名称

每个函数都有一个参数标签以及一个参数名称,参数标签在调用函数的时候使用,调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用,默认情况下,函数参数使用参数名称来作为参数标签。

  • 指定参数标签

可以在参数名称前指定参数标签,中间用空格分隔:

func someFunction(argumentLabel parameterName: Int) {
// 在函数体内,parameterName 代表参数值
}

一个新版greet例子:

func greet(person: String, from hometown: String) -> String {
return "Hello \(person)!  Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 打印“Hello Bill!  Glad you could visit from Cupertino.”

参数标签能够让函数在调用是更有表达力。

  • 忽略参数标签

如果不希望为某个参数添加一个标签,使用下划线代替:

func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
 // 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
}
someFunction(1, secondParameterName: 2)
  • 默认参数值

可以在函数体中通过给参数赋值来为任意一个参数定义默认值,默认值被定义后,调用这个函数时可以忽略这个参数。一般将带有默认值的参数放在末尾。

  • 可变参数

可变参数可以接受0个或者多个值,函数调用时,可以用可变参数来指定函数参数可以被传入不确定的输入值。在变量类型名后加入(…)表示可变参数。

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)
// 返回 3.0, 是这 5 个数的平均数。
arithmeticMean(3, 8.25, 18.75)
// 返回 10.0, 是这 3 个数的平均数。

notice:一个函数最多由一个可变参数。

  • 输入输出参数

如果函数参数是常量,则不能在函数内修改。如果想要修改参数的值,并且想在这些修改在函数调用后任然存在,需要将参数定义为输入输出参数。

定义一个输入输出参数时,在参数顶以前加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)")
// 打印“someInt is now 107, and anotherInt is now 3”

函数类型

每个函数都有中特定的函数类型,函数的类型由函数的参数类型和返回类型组成。

func addTwoInts(_ a: Int, _ b: Int) -> Int {
    return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
    return a * b
}

这两个函数的类型为(Int,Int)->Int

func printHelloWorld() {
    print("hello, world")
}
//函数类型为 ()-> void
  • 使用函数类型

使用函数类型可以:定义一个类型为函数的常量或者变量,将适当的函数赋值给他。

var mathFunction: (Int, Int) -> Int = addTwoInts

上面代码解读为: 定义一个mathFunction的变量,类型是一个有两个Int的参数,并返回一个Int的函数。让这个新变量指向addTwoInts函数。

现在可以使用mathFunction调用被复制的函数。

print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"

也可以将相同类型匹配的不同函数赋值给同一个变量:

mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"

也可以简单写法,使用swift的推断机制,推出变量为函数类型:

let anotherMathFunction = addTwoInts
// anotherMathFunction 被推断为 (Int, Int) -> Int 类型
  • 函数类型作为参数类型

使用(Int,Int)->Int 这样的函数作为另一个函数的参数类型,这样可以将函数的一部分实现留给函数的调用者提供。如下例子:

func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// 打印“Result: 8”

如上定义了一个 printMathResult(::_:)函数,有三个参数,第一个参数为函数类型,可以传入任何类型的这个函数。它不关心传入的函数如何实现,只关心传入函数是不是一个正确的类型。

  • 函数类型作为返回类型

可以用函数类型作为另一个函数的返回类型,需要做的是在返回箭头(->)后写一个完成的函数类型,例子如下:

func stepForward(_ input: Int) -> Int {
    return input + 1
}
func stepBackward(_ input: Int) -> Int {
    return input - 1
}

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}
//chooseStepFunction返回类型是(Int)->(Int),

现在可以使用如下方式调用该函数:

var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero 现在指向 stepBackward() 函数。

现在 moveNearToZero是一个函数类型,可以像函数那样使用它:

print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
  • 嵌套函数

函数可以定义在别的函数体中,称为嵌套函数,默认情况下,嵌套函数对外界不可见,但是可以被他们的外围函数调用,一个外围函数可以返回他的某个嵌套函数,使得这个函数可以在其他域中被使用。下面例子chooseStepFunction(backward:) 函数:

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)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!

闭包

闭包是自包含的函数代码块,可以在代码中被传递和使用。闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。swift会管理在捕获过程中涉及的所有内存操作。前面介绍的全局和嵌套函数实际也是特殊的闭包。闭包采用如下三种形式之一:

1. 全局函数是一个有名字但不会捕获任何值得闭包
2. 嵌套函数是一个有名字并可以捕获其封闭函数域内值得闭包
3. 闭包表达式是一个利用轻量级语法所写的可以捕获上写问中变量或常量的匿名闭包。

swift的闭包表达式风格简单,鼓励在常见场景中进行语法优化,主要优化如下:

1. 利用上下文推断参数和返回值类型
2. 隐式返回单表达式闭包,即表达式闭包可以省略return关键字
3. 参数名称缩写
4. 尾随闭包语法

闭包表达式

闭包表达式是一种构建内敛闭包的方式,语法简洁,在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式,下面例子通过对sorted(by:)这个案例的多次迭代改进来展示这个过程:

swift标准库提供了 sorted(by:)的方法。会基于提供的闭包表达式的判断结果对数组中的值进行排序,一旦完成排序过程,该方法返回一个与就数组大小相同类型的新数组,该数组的元素有着正确的排序顺序,原数组不会被该方法修改。

sorted(by:)方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔值表明排序顺序。

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
} //倒序排列
var reversedNames = names.sorted(by: backward)
// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
  • 闭包表达式语法

闭包表达式有如下一般形式:

{ (parameters) -> return type in
    statements
}

闭包表达式参数可以是inout参数,但不能设定默认值,如果命名了可变参数,可以使用可变参数,元组也可以作为参数和返回值。如下是backward函数对应闭包表达式代码:

reversedNames = names.sorted(by: { (s1: String, s2:     String) -> Bool in
        return s1 > s2
})

需要注意的是内敛闭包参数和返回值类型声明与backward(::)类型声明相同。但在内连闭包表达式中,函数和返回值类型都写在大括号内而不是大括号外闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。

  • 根据上下文推断类型

sorted(by:)方法的参数是传入的,swift可以推断其参数和返回值类型,sorted被一个字符串数组调用,因此参数必须为(string,string)->bool类型的函数。这意味者(string,string) 和bool类型不需要作为闭包表达式的一部分,因为所有的类型都可以被正确推断,返回箭头和围绕在参数周围的括号也可以省略:

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )//类型推断省略了参数类型以及箭头

实际上,通过闭包表达式构造的闭包作为参数传递给函数或者方法时,总是可以推断出闭包的参数和返回值类型。因此可以不需要完整的格式构造内联闭包。

  • 但表达式闭包的隐式返回

单行表达式闭包可以通过省略return来隐式返回单行表达式的结果。

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
  • 参数名称缩写

swift自动为内联闭包提供了参数名称缩写,可以通过$0,$1,$2…来顺序调用闭包参数。

如果在闭包表达式中使用名称缩写,可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也可以省略,因为此时闭包表达式完全由闭包函数体构成。

reversedNames = names.sorted(by: { $0 > $1 } )
  • 运算符方法

swift中的string类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个string类型的参数并返回bool类型。刚好满足sorted函数的参数要求。因此可以如下:

reversedNames = names.sorted(by: >)
  • 尾随闭包

如果需要将一个很长的闭包表达式作为最后一个参数传递给函数,这个闭包替换成尾随闭包的形式效果更好。尾随闭包格式如下:

    func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // 闭包主体部分
}

在闭包表达式语法章节字符串闭包可以作为尾随闭包的形式改写为:

reversedNames = names.sorted() { $0 > $1 }

当闭包表达式时函数或者方法的唯一参数,当你使用尾随闭包时,可以省略为如下:

reversedNames = names.sorted { $0 > $1 } //省略括号

当闭包非常长不能一行书写时,尾随闭包非常有用。举例来说,下面介绍了如何在map(_:)方法中使用尾随闭包将Int类型数组[16,58,510]转换为包含对应string类型的值的数组。

let digitNames = [
0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

上述代码创建了一个整型数位和他们英文名映射的字典,和一个转换数组。现在可以通过传递一个尾随闭包给numbers数组的map(_:)方法来创建对应的字符串版本数组。

let strings = numbers.map {
(number) -> String in
var number = number
var output = ""
repeat {
    output = digitNames[number % 10]! + output
    number /= 10
} while number > 0
return output
}
// strings 常量被推断为字符串类型数组,即 [String]
// 其值为 ["OneSix", "FiveEight", "FiveOneZero"]

上述例子中,number为前面定义的局部变量,因此可以在闭包函数体内对其进行修改。闭包表达式指定了返回类型为string。闭包表达式每次调用时创建一个output并返回结果。

  • 值捕获

闭包可以在其被定义的上下文中捕获常量或者变量。即使定义这些常量的原作用域已经不存在,但是闭包任然可以在闭包函数体内引用和修改这些值。swift中捕获值得最简单形式是嵌套函数,嵌套函数可以捕获其外部函数得所有参数以及定义得常量和变量。

  • 闭包是引用类型

无论是将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或者变量的值设置为对应函数或闭包的引用。这意味着将闭包赋值给两个不同的常量或者变量,两个值都指向同一个闭包。

逃逸闭包,自动闭包参考https://swiftgg.gitbook.io/swift/swift-jiao-cheng/07_closures