Swift学习笔记(三)
六 函数
Swift的统一的功能语法足够灵活来表达任何东西,无论是甚至没有参数名称的简单的C风格的函数表达式,还是需要为每个本地参数和外部参数设置复杂名称的Objective-C语言风格的函数。参数提供默认值,以简化函数调用,并通过设置在输入输出参数,在函数执行完成时修改传递的变量。
Swift中的每个函数都有一个类型,包括函数的参数类型和返回类型。您可以方便的使用此类型像任何其他类型一样,这使得它很容易将函数作为参数传递给其他函数,甚至从函数中返回函数类型。函数也可以写在其他函数中来封装一个嵌套函数用以范围内有用的功能。
Swift使用func关键字声明函数:
没有返回值的函数
函数也不需要定义一个返回类型。这里有一个版本的sayHello的函数,称为waveGoodbye,它会输出自己的字符串值而不是函数返回:
func sayGoodbye(personName: String) {
print("Goodbye, \(personName)!")
}
sayGoodbye("Dave")
prints "Goodbye, Dave!"
因为它并不需要返回一个值,该函数的定义不包括返回箭头( – >)和返回类型。
func meet(name: String, day: String) ->String {
return "hey \(name)! today is \(day)"
}
print(meet("Bob", day:"Tuesday"))
通过元组(Tuple)返回多个值:
func getGasPrices() -> (Double, Double, Double) {
return (2.3, 2.33, 2.333)
}
print(getGasPrices())
支持带有变长参数的函数
func sumOf(numbers: Int...) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
print(sumOf(814))
print(sumOf(42, 233, 12))
外部参数名
有时当你调用一个函数将每个参数进行命名是非常有用的,以表明你传递给函数的每个参数的目的。
如果你希望用户函数调用你的函数时提供参数名称,除了设置本地地的参数名称,也要为每个参数定义外部参数名称。你写一个外部参数名称在它所支持的本地参数名称之前,之间用一个空格来分隔
注意
考虑到使用外部参数名称的初衷就是为了在别人第一次阅读你的代码时并不知道你函数参数的目的是什么。
但当函数调用时如果每个参数的目的是明确的和毫不含糊的,你并不需要指定外部参数名称。
省略外部参数名
Swift 2.0提供省略外部参数名可能,在定义函数时,使用下划线(_)表示外部参数名,示例代码如下:
func rectangleArea(width:Double,_ height:Double) -> Double {
let area = width * height
return area
}
这样函数在调用时就可以省略外部参数名,代码如下:
print("320x480的长方形的面积:\(rectangleArea(320, 480))")
在定义函数时第一个参数不需要使用下划线(_),默认第一个参数名是省略的,其他参数名要想省略则需要使用下划线(_)符号。
可变参数
一个可变参数的参数接受零个或多个指定类型的值。当函数被调用时,您可以使用一个可变参数的参数来指定该参数可以传递不同数量的输入值。写可变参数的参数时,需要参数的类型名称后加上点字符(…)。
传递一个可变参数的参数的值时,函数体中是以提供适当类型的数组的形式存在。例如,一个可变参数的名称为numbers和类型为Double…在函数体内就作为名为numbers类型为Double[]的常量数组。
下面的示例计算任意长度的数字的算术平均值:
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)
arithmeticMean(3, 8, 19, 30, 2)
注意
函数可以最多有一个可变参数的参数,而且它必须出现在参数列表的最后以避免多参数函数调用时出现歧义。
如果函数有一个或多个参数使用默认值,并且还具有可变参数,将可变参数放在列表的最末尾的所有默认值的参数之后。
函数类型
每个函数都有一个特定的类型,包括参数类型和返回值类型,比如:
func addTwoInts(a: Int, b: Int) -> Int {
return a + b
}
func multiplyTwoInts(a: Int, b: Int) -> Int {
return a * b
}
这个例子定义了两个简单的数学函数addTwoInts和multiplyTwoInts。每个函数接受两个int参数,返回一个int值,执行相应的数学运算然后返回结果
这两个函数的类型是(Int, Int)->Int可以解释为:
这个函数类型它有两个int型的参数,并返回一个int类型的值
下面这个例子是一个不带任何参数和返回值的函数:
func printHelloWorld() {
print("hello, world")
}
这个函数的类型是()->(),或者函数没有参数,返回void。函数没有显式地指定返回类型,默认为void,在Swift中相当于一个空元组,记为()。
使用函数类型
在swift中你可以像任何其他类型一样的使用函数类型。例如,你可以定义一个常量或变量为一个函数类型,并指定适当的函数给该变量:
var mathFunction: (Int, Int) -> Int = addTwoInts
可以解读为:
“定义一个名为mathFunction变量,该变量的类型为’一个函数,它接受两个int值,并返回一个int值。’设置这个新的变量来引用名为addTwoInts函数的功能。”
该mathFunction函数具有与addTwoInts函数相同类型的变量,所以这个赋值能通过Swift的类型检查。
现在你可以调用指定的函数名称为mathFunction:
print("Result: \(mathFunction(2, 3))")
不同的函数相同的匹配类型可以分配给相同的变量,也同样的适用于非函数性类型:
mathFunction = multiplyTwoInts
print("\(mathFunction(2, 3))")
与其他类型一样,你可以把它迅速定义成函数类型当你为常量或变量分配一个函数时:
let anotherMathFunction = addTwoInts
函数类型的参数
可以使用一个函数类型,如(Int, Int)->Int作为另一个函数的参数类型。这使你预留了一个函数的某些方面的实现,让调用者调用函数时提供。
func printMathResult(mathFunction: (Int, Int) -> Int, a: Int, b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, a: 3, b: 5)
这个例子中定义了一个名为printMathResult函数,它有三个参数。第一个参数名为mathFunction,类型为(Int, Int)->Int。您可以传入符合条件的任何函数类型作为此函数的第一个参数。第二和第三个参数a、b都是int类型。被用作于提供数学函数的两个输入值。
当printMathResult被调用时,它传递addTwoInt函数,以及整数值3和5。它使用3和5,调用addTwoInt函数,并打印函数运行的结果8。
printMathResult的作用是调用一个适当类型的数学函数并打印相应结果。那是什么功能的实现其实并不重要,你只要给以正确的类型匹配就行。这使printMathResult以调用者类型安全的方式转换了函数的功能。
函数类型的返回值
可以使用一个函数类型作为另一个函数的返回类型。返回的函数(->)即你的返回箭头后,立即写一个完整的函数类型就做到这一点。
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 currentValue = 3
let moveNearerToZero = chooseStepFunction(currentValue > 0)
print("Counting to zero:")
Counting to zero:
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
嵌套函数
其实你可以在其他函数中定义函数,被称为嵌套函数。
嵌套函数默认对外界是隐藏的,但仍然可以调用和使用其内部的函数。内部函数也可以返回一个嵌套函数,允许在嵌套函数内的另一个范围内使用
func chooseStepFunction1(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
var currentValue1 = -4
let moveNearerToZero1 = chooseStepFunction1(currentValue1 > 0)
moveNearerToZero now refers to the nested stepForward() function
while currentValue1 != 0 {
print("\(currentValue1)... ")
currentValue1 = moveNearerToZero1(currentValue1)
}
print("zero!")
七闭包
: Playground - noun: a place where people can play
闭包(Closures)是独立的函数代码块,能在代码中传递及使用。Swift中的闭包与C和Objective-C中的代码块及其它编程语言中的匿名函数相似。
闭包可以在上下文的范围内捕获、存储任何被定义的常量和变量引用。因这些常量和变量的封闭性,而命名为“闭包(Closures)”。Swift能够对所有你所能捕获到的引用进行内存管理。
全局函数和嵌套函数已在 Functions(函数)中介绍过,实际上这些都是特殊的闭包函数
全局函数都是闭包,特点是有函数名但没有捕获任何值。
嵌套函数都是闭包,特点是有函数名,并且可以在它封闭的函数中捕获值。
闭包表达式都是闭包,特点是没有函数名,可以使用轻量的语法在它所围绕的上下文中捕获值。
Swift的闭包表达式有着干净,清晰的风格,并常见情况下对于鼓励简短、整洁的语法做出优化。这些优化包括:
推理参数及返回值类型源自上下文
隐式返回源于单一表达式闭包
简约参数名
尾随闭包语法
Sort函数
Swift的标准函数库提供了一个名为sort的函数,它通过基于输出类型排序的闭包函数,给已知类型的数组数据的值排序。一旦完成排序工作,会返回一个同先前数组相同大小,相同数据类型,并且的新数组,并且这个数组的元素都在正确排好序的位置上。
以下的闭包表达式通过sort函数将String值按字母顺序进行排序作说明,这是待排序的初始化数组。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sort函数需要一个参数:接收两个参数的闭包函数,这两个参数的数据类型都同于数组元素。并且返回一个Bool表明是否第一个参数应排在第二个参数前或后。
names.sort(<#T##isOrderedBefore: (String, String) -> Bool##(String, String) -> Bool#>)
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sort(backwards)
如果backwards函数参数 s1 大于 s2,则返回true值,表示在新的数组排序中 s1 应该出现在 s2 前。 字符中的 “大于” 表示 “按照字母顺序后出现”。 这意味着字母 “B” 大于字母 “A”, 字符串 “Tom” 大于字符串 “Tim”。 其将进行字母逆序排序,”Barry” 将会排在 “Alex” 之后,以此类推。
但这是一个相当冗长的方式,本质上只是做了一个简单的单表达式函数 :(a > b)。 下面的例子中,我们利用闭合表达式可以相比上面的例子更效率的构造一个内联排序闭包。
/*
闭包表达式语法
闭合表达式语法具有以下一般构造形式:
{ (parameters) -> return type in
statements
}
闭包表达式语法可以使用常量参数、变量参数和 inout 类型作为参数,但皆不可提供默认值。 如果你需要使用一个可变的参数,可将可变参数放在最后,元组类型也可以作为参数和返回值使用。
*/
下面的例子展示了上面的 backwards 函数对应的闭包表达式构造函数代码
reversed = names.sort({ (s1: String, s2: String) -> Bool in
return s1 > s2
})
需要注意的是声明内联闭包的参数和返回值类型与 backwards 函数类型声明相同。 在这两种方式中,都写成了 (s1: String, s2: String) -> Bool类型。 然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
闭包的函数体部分由关键字 in 引入。 该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
因为这个闭包的函数体非常简约短所以完全可以将上面的backwards函数缩写成一行连贯的代码
reversed = names.sort({ (s1: String, s2: String) -> Bool in return s1 > s2 } )
print(reversed)
根据上下文推断类型
因为排序闭包是作为函数的参数进行传入的,Swift可以推断其参数和返回值的类型。 sort 期望参数是类型为 (String, String) -> Bool 的函数,因此实际上 String, String 和 Bool 类型并不需要作为闭包表达式定义中的一部分。 因为所有的类型都可以被正确推断,返回箭头 (->) 和 围绕在参数周围的括号也可以被省略:
reversed = names.sort({ s1, s2 in return s1 < s2 })
print(reversed)
实际情况下,通过构造内联闭包表达式的闭包作为函数的参数传递给函数时,都可以判断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。
同样,如果你希望避免阅读函数时可能存在的歧义, 你可以直接明确参数的类型。
这个排序函数例子,闭包的目的是很明确的,即排序被替换,而且对读者来说可以安全的假设闭包可能会使用字符串值,因为它正协助一个字符串数组进行排序。
单行表达式闭包可以省略 return
单行表达式闭包可以通过隐藏 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
reversed = names.sort({ s1, s2 in s1 > s2 })
print(reversed)
在这个例子中,sort 函数的参数函数类型明确了闭包必须返回一个 Bool 类型值。 因为闭包函数体只包含了一个单一表达式 (s1 > s2),该表达式返回 Bool 类型值,因此这里没有歧义,return关键字可以省略。
参数名简写
Swift 自动为内联函数提供了参数名称简写功能,可以直接通过 $0,$1,$2等名字来引用闭包的参数值。
如果在闭包表达式中使用参数名称简写,可以在闭包参数列表中省略对其的定义,并且对应参数名称简写的类型会通过函数类型进行推断。 in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成
reversed = names.sort({$0 < $1})
print(reversed)
在这个例子中,$0 和 $1 表示闭包中第一个和第二个 String 类型的参数。
运算符函数
运算符函数实际上是一个更短的方式构造以上的表达式。
reversed = names.sort(>)
print(reversed)
Trailing 闭包
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用 trailing 闭包来增强函数的可读性。
Trailing 闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个参数调用。
func someFunctionThatTakesAClosure(closure: () -> ()) {
function body goes here
}
here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure({
closure's body goes here
})
here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
trailing closure's body goes here
}
注意
如果函数只需要闭包表达式一个参数,当您使用 trailing 闭包时,您甚至可以把 () 省略掉。
当闭包非常长以至于不能在一行中进行书写时,Trailing 闭包就变得非常有用。 举例来说,Swift 的 Array 类型有一个 map 方法,其获取一个闭包表达式作为其唯一参数。数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。 具体的映射方式和返回值类型由闭包来指定。
当提供给数组闭包函数后,map 方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。
下例介绍了如何在 map 方法中使用 trailing 闭包将 Int 类型数组 [16,58,510] 转换为包含对应 String 类型的数组 ["OneSix", "FiveEight", "FiveOneZero"]:
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]
你现在可以通过传递一个 trailing 闭包给 numbers 的 map 方法来创建对应的字符串版本数组。需要注意的时调用 numbers.map不需要在 map 后面包含任何括号,因为只需要传递闭包表达式这一个参数,并且该闭包表达式参数通过 trailing 方式进行撰写:
let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
print(strings)
map 在数组中为每一个元素调用了闭包表达式。您不需要指定闭包的输入参数 number 的类型,因为可以通过要映射的数组类型进行推断。
闭包 number 参数被声明为一个变量参数 ,因此可以在闭包函数体内对其进行修改。闭包表达式制定了返回值类型为 String,以表明存储映射值的新数组类型为 String。
闭包表达式在每次被调用的时候创建了一个字符串并返回。其使用求余运算符 (number % 10) 计算最后一位数字并利用digitNames 字典获取所映射的字符串。
注意:
字典 digitNames 下标后跟着一个叹号 (!),因为字典下标返回一个可选值 (optional value),表明即使该 key不存在也不会查找失败。 在上例中,它保证了 number % 10 可以总是作为一个 digitNames 字典的有效下标 key。 因此叹号可以用于强展开 (force-unwrap) 存储在可选下标项中的 String 类型值。
从 digitNames 字典中获取的字符串被添加到输出的前部,逆序建立了一个字符串版本的数字。 (在表达式 number % 10中,如果number为16,则返回6,58返回8,510返回0)。
number 变量之后除以10。 因为其是整数,在计算过程中未除尽部分被忽略。 因此 16变成了1,58变成了5,510变成了51。
整个过程重复进行,直到 number /= 10 为0,这时闭包会将字符串输出,而map函数则会将字符串添加到所映射的数组中。
上例中 trailing 闭包语法在函数后整洁封装了具体的闭包功能,而不再需要将整个闭包包裹在 map 函数的括号内。
获取值
闭包可以在其定义的范围内捕捉(引用/得到)常量和变量,闭包可以引用和修改这些值,即使定义的常量和变量已经不复存在了依然可以修改和引用。
在Swift中最简单形式是一个嵌套函数,写在另一个函数的方法里面。嵌套函数可以捕获任何外部函数的参数,也可以捕获任何常量和变量在外部函数的定义。
看下面这个例子,一个函数方法为makeIncrementor、这是一个嵌套函数,在这个函数体内嵌套了另一个函数方法:incrementor,在这个incrementor函数体内有两个参数: runningTotal和amount,实际运作时传进所需的两个参数后,incrementor函数每次被调用时都会返回一个runningTotal值提供给外部的makeIncrementor使用:
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
而函数makeincrementor的返回类型值我们可以通过函数名后面的()-> int得知返回的是一个Int类型的值
我们可以看见makeincrementor这个函数体内首先定义了一个整型变量:runningtotal,初始值为 0 ,而incrementor()函数最终运行的出来的返回值会赋值给这个整型变量。
makeincrementor函数()中向外部抛出了一个forIncrement参数供外部穿参进来、一旦有值进入函数体内会被函数实例化替代为amount,而amount会被传递进内嵌的incrementor函数体中与整型常量runningTotal相加得到一个新的runningTotal并返回。而我们这个主函数要返回的值是Int类型,runningTotal直接作为最终值被返回出去、makeincrementor函数()执行完毕
makeIncrementor函数()在其内部又定义了一个新的函数体incrementor,作用就是将外部传递过来的值amount 传进incrementor函数中与整形常量runningTotal相加得到一个新的runningTotal,
单独的看incrementor函数、
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
因为incrementor函数没有任何的参数,但是在它的函数方法体内却指向runningTotal和amount,显而易见、这是incrementor函数获取了外部函数的值amount,incrementor不能去修改它但是却可以和体内的runningTotal相加得出新的runningTotal值返回出去。
不过,由于runningtotal每次被调用时都会相加改变一次实际值,相应地incrementor函数被调用时会去加载最新的runningtotal值,而不再是第一次初始化的0.并且需要保证每次runningTotal的值在makeIncrementor函数体内不会丢失直到函数完全加载完毕。要能确保在函数体内下一次引用时上一次的值依然还在。
注意
Swift中需要明确知道什么时候该引用什么时候该赋值,在incrementor函数中你不需要注解amount 和runningTotal。Swift还负责处理当函数不在需要runningTotal的时候,内存应该如何去管理。
let incrementByTen = makeIncrementor(forIncrement: 10)
incrementByTen()
引用类型闭包
在上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量在闭包的状态下依然可以被修改。为何?很简单,因为函数和闭包是引用类型。
当你指定一个函数或一个闭包常量/变量时、实际上是在设置该常量或变量是否为一个引用函数。在上面的例子中,它是闭合的选择,incrementByTen指的是恒定的,而不是封闭件本身的内容。
这也意味着,如果你分配一个封闭两种不同的常量或变量,这两个常量或变量将引用同一个闭包
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()