函数的定义
基本概念
- 函数是一个独立的代码块,用来执行特定的任务。通过给函数一个名字来定义它的功能,并且在需要 的时候,通过这个名字来“调用”函数执行它的任务
- Swift 统一的函数语法十分灵活,可以表达从简单的无形式参数的 C 风格函数到复杂的每一个形式参 数都带有局部和外部形式参数名的 Objective-C 风格方法的任何内容。形式参数能提供一个默认的值 来简化函数的调用,也可以被当作输入输出形式参数被传递,它在函数执行完成时修改传递来的变 量。
- Swift 中的每一个函数都有类型,由函数的形式参数类型和返回类型组成。你可以像 Swift 中其他类 型那样来使用它,这使得你能够方便的将一个函数当作一个形式参数传递到另外的一个函数中,也可 以在一个函数中返回另一个函数。函数同时也可以写在其他函数内部来在内嵌范围封装有用的功能。
定义和调用函数
- 当你定义了一个函数的时候,你可以选择定义一个或者多个命名的分类的值作为函数的输入 (所谓的形式参数),并且/或者定义函数完成后将要传回作为输出的值的类型(所谓它的返回 类型)
-
每一个函数都有一个函数名,它描述函数执行的任务。要使用一个函数,你可以通过“调用” 函数的名字并且传入一个符合函数形式参数类型的输入值(所谓实际参数)来调用这个函数。 给函数提供的实际参数的顺序必须符合函数的形式参数列表顺序。
无形式参数的函数
- 函数没有要求必须输入一个参数,可以没有形式参数。
-
函数的定义仍然需要在名字后边加一个圆括号,即使它不接受形式参数也得这样做。当函数被 调用的时候也要在函数的名字后边加一个空的圆括号。
多形式参数的函数
- 函数可以输入多个形式参数,可以写在函数后边的圆括号内,用逗号分隔。
-
形参默认是let,也只能是let
无返回值的函数
- 函数定义中没有要求必须有一个返回类型。
- 不需要返回值,函数在定义的时候就没有包含返回箭头( ->)或者返回类型。
-
严格来讲,函数 greet(person:)还是有一个返回值的,尽管没有定义返回值。没有定义返回类 型的函数实际上会返回一个特殊的类型 Void。它其实是一个空的元组,作用相当于没有元素的 元组,可以写作 () 。
多返回值的函数
-
为了让函数返回多个值作为一个复合的返回值,你可以使用元组类型作为返回类型。
可选元组返回类型
-
如果元组在函数的返回类型中有可能“没有值”,你可以用一个可选元组返回类型来说明整个元组的可能 是 nil 。写法是在可选元组类型的圆括号后边添加一个问号( ?)例如 (Int, Int)? 或者 (String, Int, Bool)? 。
隐式返回的函数
-
如果整个函数体是一个单一表达式,那么函数隐式返回这个表达式。
函数实际参数标签和形式参数名
- 每一个函数的形式参数都包含实际参数标签和形式参数名。实际参数标签用在调用函数 的时候;在调用函数的时候每一个实际参数前边都要写实际参数标签。形式参数名用在 函数的实现当中。默认情况下,形式参数使用它们的形式参数名作为实际参数标签。
-
所有的形式参数必须有唯一的名字。尽管有可能多个形式参数拥有相同的实际参数标 签,唯一的实际参数标签有助于让你的代码更加易读。
指定实际参数标签
- 在提供形式参数名之前写实际参数标签,用空格分隔。
- 如果你为一个形式参数提供了实际参数标签,那么这个实际参数就必须在调用函数的 时候使用标签。
-
实际参数标签的使用能够让函数的调用更加明确,更像是自然语句,同时还能提供更 可读的函数体并更清晰地表达你的意图。
省略实际参数标签
-
如果对于函数的形式参数不想使用实际参数标签的话,可以利用下划线( _ )来为 这个形式参数代替显式的实际参数标签。
默认形式参数值
- 你可以通过在形式参数类型后给形式参数赋一个值来给函数的任意形式参数定义一 个默认值。
-
如果定义了默认值,你就可以在调用函数时候省略这个形式参数。
可变形式参数
- 一个可变形式参数可以接受零或者多个特定类型的值。当调用函数的时候你可以利 用可变形式参数来声明形式参数可以被传入值的数量是可变的。可以通过在形式参 数的类型名称后边插入三个点符号( ...)来书写可变形式参数。
-
传入到可变参数中的值在函数的主体中被当作是对应类型的数组。
输入输出形式参数
- 可变形式参数只能在函数的内部做改变。如果你想函数能够修改一个形式参数的值,而且你想这些改变在函 数结束之后依然生效,那么就需要将形式参数定义为输入输出形式参数。
- 在形式参数定义开始的时候在前边添加一个 inout 关键字可以定义一个输入输出形式参数。输入输出形式参 数有一个能输入给函数的值,函数能对其进行修改,还能输出到函数外边替换原来的值。
- 你只能把变量作为输入输出形式参数的实际参数,在将变量作为实际参数传递给输入输出形式参数的时候, 直接在它前边添加一个和符号 ( &) 来明确可以被函数修改
- 输入输出形式参数不能有默认值,可变形式参数不能标记为 inout
函数类型和内嵌函数
函数类型
-
每一个函数都有一个特定的函数类型,它由形式参数类型,返回类型组成。
使用函数类型
-
你可以像使用 Swift 中的其他类型一样使用函数类型。例如,你可以给一个常量或变 量定义一个函数类型,并且为变量指定一个相应的函数。
函数类型作为形式参数类型
-
你可以利用使用一个函数的类型例如 (Int, Int) -> Int 作为其他函数的形式参数类型。 这允许你预留函数的部分实现从而让函数的调用者在调用函数的时候提供
函数类型作为返回类型
-
你可以利用函数的类型作为另一个函数的返回类型。写法是在函数的返回箭头( ->) 后立即写一个完整的函数类型。
内嵌函数
- 可以在函数的内部定义另外一个函数。这就是内嵌函数。
-
内嵌函数在默认情况下在外部是被隐藏起来的,但却仍然可以通过包裹它们的函数来 调用它们。包裹的函数也可以返回它内部的一个内嵌函数来在另外的范围里使用。
函数重载
- 函数名相同
- 参数个数不同 或参数类型不同或者参数标签不同
func sum(v1:Int, v2: Int) -> Int {
v1 + v2
}
//参数个数不同
func sum(v1:Int, v2: Int, v3: Int) -> Int {
v1 + v2 + v3
}
//参数类型不同
func sum(v1:Int, v2: Double) -> Double {
Double(v1) + v2
}
//参数类型不同
func sum(v1:Double, v2: Int) -> Double {
v1 + Double(v2)
}
//参数标签不同
func sum(_ v1:Int, _ v2: Int) -> Int {
v1 + v2
}
//参数标签不同
func sum(a:Int, b: Int) -> Int {
a + b
}
注意点:
默认参数值和函数重载一起使用产生二义性时,编译器并不会报错
可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错。
内联函数
如果开启了编译器优化(Release模式默认会开启优化),编译器会自动将某些函数变成内联函数,将函数调用展开成函数体
Build Settings -> 搜索optimization -> 在Optimization Level下面修改
- 哪些函数不会被自动内联?
函数体比较长
包含递归调用
包含动态派发
等等
//永远不会被内联(即使开启了编译器优化)
@inline(never) func test() {
print("test")
}
//开启编译器优化后,即使代码很长,也会被内联(递归调用函数,动态派发的函数除外)
@inline(__always) func test2() {
print("test")
}
闭包
闭包的概念
- 闭包是可以在你的代码中被传递和引用的功能性独立代码块。
- 闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用,这也就是所谓的闭 合并包裹那些常量和变量,因此被称为“闭包”,Swift 能够为你处理所有关于捕获 的内存管理的操作。
- 在函数章节中有介绍的全局和内嵌函数,实际上是特殊的闭包。闭包符合如下三种形式中的一种:
- 全局函数是一个有名字但不会捕获任何值的闭包;
- 内嵌函数是一个有名字且能从其上层函数捕获值的闭包;
- 闭包表达式是一个轻量级语法所写的可以捕获其上下文中常量或变量值的没有名字的闭包。
闭包表达式
- 闭包表达式是一种在简短行内就能写完闭包的语法。
-
Swift 的标准库提供了一个叫做 sorted(by:) 的方法,会根据你提供的排序闭包将已知类型 的数组的值进行排序。一旦它排序完成, sorted(by:) 方法会返回与原数组类型大小完全相 同的一个新数组,该数组的元素是已排序好的。原始数组不会被 sorted(by:) 方法修改。
闭包表达式语法
-
闭包表达式语法能够使用常量形式参数、变量形式参数和输入输出形式参数,但不能 提供默认值。可变形式参数也能使用,但需要在形式参数列表的最后面使用。元组也 可被用来作为形式参数和返回类型。
闭包表达式语法版本的 backward
- 将之前 backward(::) 函数改为闭包表达版本
从语境中推断类型
- 因排序闭包为实际参数来传递给函数,故 Swift 能推断它的形式参数类型和返回类型
-
sorted(by:) 方法期望它的形式参数是一个 (String, String) -> Bool 类型的函数。这 意味着 (String, String)和 Bool 类型不需要被写成闭包表达式定义中的一部分,因为 所有的类型都能被推断,返回箭头 ( ->) 和围绕在形式参数名周围的括号也能被省略
从单表达式闭包隐式返回
-
单表达式闭包能够通过从它们的声明中删掉 return 关键字来隐式返回它们单个表达式 的结果。
简写实际参数名
- Swift 自动对行内闭包提供简写实际参数名,可以通过 1 , $2 等名字来引用闭包 的实际参数值。
运算符函数
-
Swift 的 String 类型定义了关于大于号( >)的特定字符串实现,让其作为一个有两 个 String 类型形式参数的函数并返回一个 Bool 类型的值。这正好与 sorted(by:) 方 法的形式参数需要的函数相匹配。因此,你能简单地传递一个大于号,并且 Swift 将 推断你想使用大于号特殊字符串函数实现
尾随闭包
-
如果你需要将一个很长的闭包表达式作为函数最后一个实际参数传递给函数,使用尾 随闭包将增强函数的可读性。尾随闭包是一个被书写在函数形式参数的括号外面(后 面)的闭包表达式。
捕获值
-
一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作 用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。
作为一种优化,如果一个值没有改变或者在闭包的外面,Swift 可能会使用这个值的 拷贝而不是捕获。
-
Swift也处理了变量的内存管理操作,当变量不再需要时会被释放。
-
如果你建立了第二个 incrementer ,它将会有一个新的、独立的 runningTotal 变量的 引用。
闭包是引用类型
在 Swift 中,函数和闭包都是引用类型。
-
无论你什么时候赋值一个函数或者闭包给常量或者变量,你实际上都是将常量和变量 设置为对函数和闭包的引用。
如果你分配了一个闭包给类实例的属性,并且闭包通过引用该实例或者它的成员来捕 获实例,你将在闭包和实例间会产生循环引用。
逃逸闭包和自动闭包
逃逸闭包
当闭包作为一个实际参数传递给一个函数的时候,并且它会在函数返回之后调用,我 们就说这个闭包逃逸了。当你声明一个接受闭包作为形式参数的函数时,你可以在形 式参数前写 @escaping 来明确闭包是允许逃逸的。
-
闭包可以逃逸的一种方法是被储存在定义于函数外的变量里。比如说,很多函数接收 闭包实际参数来作为启动异步任务的回调。函数在启动任务后返回,但是闭包要直到 任务完成——闭包需要逃逸,以便于稍后调用。
让闭包 @escaping 意味着你必须在闭包中显式地引用 self
自动闭包
自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包。它不 接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值。
-
这个语法的好处在于通过写普通表达式代替显式闭包而使你省略包围函数形式参数的括 号。
-
自动闭包允许你延迟处理,因此闭包内部的代码直到你调用它的时候才会运行。对于 有副作用或者占用资源的代码来说很有用,因为它可以允许你控制代码何时才进行求值。
-
当你传一个闭包作为实际参数到函数的时候,你会得到与延迟处理相同的行为。
-
通过 @autoclosure 标志标记它的形式参数使用了自动闭包。现在你可以调用函数就 像它接收了一个 String 实际参数而不是闭包。实际参数自动地转换为闭包,因为 customerProvider 形式参数的类型被标记为 @autoclosure 标记。
自动+逃逸
-
如果你想要自动闭包允许逃逸,就同时使用 @autoclosure 和 @escaping 标志。
高阶函数
map
-
对于原始集合里的每一个元素,以一个变换后的元素替换之形成一个新的集合
filter
- 对于原始集合里的每一个元素,通过判定来将其丢弃或者放进新集合
reduce
-
对于原始集合里的每一个元素,作用于当前累积的结果上
flatMap
- 对于元素是集合的集合,可以得到单级的集合
compactMap
-
过滤空值
函数式编程1
范式转换-从一个题目说起
- 读入一个文本文件,确定所有单词的使用频率并从高到低排序,打印出所有单词及其 频率的排序列表。
- 这道题目出自计算机科学史上的著名事件,是当年 Communications of the ACM 杂 志“Programming Pearls”专栏的作者 Jon Bentley 向计算机科学先驱 Donald Knuth 提出的挑战
范式转换-传统解决方案
范式转换-函数式
范式转换
- 命令式编程风格常常迫使我们出于性能考虑,把不同的任务交织起来,以便能够用一 次循环来完成多个任务。
- 而函数式编程用 map()、filter() 这些高阶函数把我们解放出来,让我们站在更高的抽 象层次上去考虑问题,把问题看得更清楚。
简洁
- 面向对象编程通过封装不确定因素来使代码能被人理解;函数式编程通过尽量减少不 确定因素来使代码能被人理解。
- 在⾯面向对象的命令式编程语⾔言⾥里里⾯面,重⽤用的单元是类和类之间沟通⽤用的消息。
- 函数式编程语⾔言实现重⽤用的思路路很不不⼀一样。函数式语⾔言提倡在有限的⼏几种关键数据结构 (如 list、set、map)上运⽤用针对这些数据结构⾼高度优化过的操作,以此构成基本的运 转机构。开发者再根据具体⽤用途,插⼊入⾃自⼰己的数据结构和⾼高阶函数去调整机构的运转⽅方 式。
- 比起一味创建新的类结构体系,把封装的单元降低到函数级别,更有利于达到细粒度的、 基础层面的重用。
- 函数式程序员喜欢用少数几个核心数据结构,围绕它们去建立一套充分优化的运转机构。面 向对象程序员喜欢不断地创建新的数据结构和附属的操作,因为压倒一切的面向对象编程范 式就是建立新的类和类间的消息。把所有的数据结构都封装成类,一方面压制了方法层面的 重用,另一方面鼓励了大粒度的框架式的重用。函数式编程的程序构造更方便我们在比较细 小的层面上重用代码。
函数式编程2
业务需求
- 假设我们有一个名字列表,其中一些条目由单个字符构成。现在的任务是,将除去单字符条 目之外的列表内容,放在一个逗号分隔的字符串里返回,且每个名字的首字母都要大写。
命令式解法
-
命令式编程是按照“程序是一系列改变状态的命令”来建模的一种编程风格。传统的 for 循 环是命令式风格的绝好例子:先确立初始状态,然后每次迭代都执行循环体中的一系列命 令。
函数式解法
-
函数式编程将程序描述为表达式和变换,以数学方程的形式建立模型,并且尽量避免可变的 状态。函数式编程语言对问题的归类不同于命令式语言。如前面所用到的几种操作 (filter、transform、convert),每一种都作为一个逻辑分类由不同的函数所代表,这些 函数实现了低层次的变换,但依赖于开发者定义的高阶函数作为参数来调整其低层次运转机 构的运作。
聊聊 Swift 的劣势-并行
对 Swift 的尝试改进
具有普遍意义的基本构造单元
- 筛选(filter)
- 映射(map)
- 折叠/化约(foldLeft/reduce等)