Swift 烧脑体操(三) - 高阶函数
前言
Swift 其实比 Objective-C 复杂很多,相对于出生于上世纪 80 年代的 Objective-C 来说,Swift 融入了大量新特性。这也使得我们学习掌握这门语言变得相对来说更加困难。不过一切都是值得的,Swift 相比 Objective-C,写出来的程序更安全、更简洁,最终能够提高我们的工作效率和质量。
Swift 相关的学习资料已经很多,我想从另外一个角度来介绍它的一些特性,我把这个角度叫做「烧脑体操」。什么意思呢?就是我们专门挑一些比较费脑子的语言细节来学习。通过「烧脑」地思考,来达到对 Swift 语言的更加深入的理解。
这是本体操的第三节,练习前请做好准备运动,保持头脑清醒。
准备运动:基础知识
在上一节里面,我们其实已经涉及到了高阶函数了。在 Wikipedia 中,是这么定义高阶函数(higher-order function)的,如果一个函数:
- 接受一个或多个函数当作参数
- 把一个函数当作返回值
那么这个函数就被称作高阶函数。下面是一个简单的排序的例子,在这个例子中,传进去的参数就是一个函数:
let numbers = [1, 4, 2, 3] |
Trailing Closure Syntax
上面的代码看着不像是函数作为参数存在,这是因为 Swift 的 Trailing Closure 特性。Swift 允许当函数的最后一个参数是闭包的时候,以紧跟 { }
的形式,将最后一个闭包的内容附加在函数后面。
所以,以下两行代码是等价的:
// 正常写法,函数是作为 sort 的参数 |
常见用法示例
高阶函数在 Swift 语言中有大量的使用场景,我们先来看一看常见的用法:
遍历
我们可以用 map
方法来对数组元素进行某种规则的转换,例如:
let arr = [1, 2, 4] |
求和
我们可以用 reduce
方法,来对数组元素进行某种规则的求和(不一定是加和)。
let arr = [1, 2, 4] |
筛选
我们可以利用 filter
方法,来对数组元素进行某种规则的过滤,例如:
let arr = [1, 2, 4] |
遍历
即使是以前最简单的遍历,我们也可以用高阶函数的写法,将遍历需要的操作,以函数参数的形式传入 forEach
方法中,例如:
let arr = [1, 2, 4] |
烧脑体操
下面我们来看看高阶函数一些比较烧脑的细节。
用高阶函数来隐藏私有变量
高阶函数使得代码逻辑可以用函数为主体来进行封装,下面我将详细解释一下这句话。
在面向对象的世界里,逻辑存在的基本单元是对象,每个对象代表着一个最小可复用模块。在对象的内部,由高内聚的成员变量和成员函数构成。这些函数相互调用,并且操作对象的内部成员变量,最终对外产生可预期的行为。
但是利用高阶函数,我们可以同样做到与对象类似的,高内聚的成员变量和成员函数,下面我就举一个具体的例子。
下面的代码中,我们用类的方式,实现了一个 Clock
类, Clock
类实现了一个 getCount
方法,每次调用的时候返回的值 +1
。为了测试代码,我们定义了两个实例 c1 和 c2,它们都可以正常输出预期的值。
class Clock { |
那么接下来,我们用高阶函数的方式,来做一下同样的事情。我们先看代码:
func getClock() -> () -> Int { |
在上面的代码中,我们这里定义了一个 getClock
函数,这个函数可以返回一个 getCount
函数。然后,不太一样的地方是,这个 getCount
函数持有了一个外部的变量 count
。于是,这个函数也变得有了状态(或者你也可以说它有了 Side Effect)。每次调用这个函数的时候,返回的值都会变化。
另一方面,因为count
变量是 getClock
这个高阶函数的内部变量,所以它并没有像全局变量一样使得封装性被打破。getClock
函数仍然可以看作一个高内部的可复用模块,并且对外隐藏了实现细节。
所以,Swift 语言的高阶函数以及闭包可以 capture 外部变量的特性,使得代码逻辑可以以函数作为主体来进行封装,这将使得我们的代码组织更加灵活。
当然,如果滥用,这也会造成代码组织变得更加混乱。
面试题
题目一
另一个烧脑的故事是来自于一个朋友的面试题。在面试中,面试官要求他用数组的 reduce
方法实现 map
的功能。
这个题目实在是非常蛋疼,不过用来烧脑倒是不错,大家感兴趣的话可以先想想,再翻下面的参考答案。
题目二
不过说回来,虽然题目一有些奇怪,但是它确实考查了对于高阶函数灵活使用以及对 reduce
方法的理解。大家还可以试试这些题目:
- 问题一:用
reduce
方法找出数组中的最大值。 - 问题二:用
reduce
方法一次求出数组中奇数的和、以及偶数乘积。
题目三
高阶函数另一个魔力就是可以链式调用,大家可以尝试这么一道题目:求一个数组中偶数的平方和。
参考答案
题目一
let arr = [1, 3, 2] |
题目二
问题二的参考答案:
let arr = [1, 3, 2, 4] let res: (Int, Int) = arr.reduce((0, 1)) { |
题目三
以下是参考答案:
let arr = [1, 3, 2, 4] |
总结
总结一下本次烧脑锻炼到的脑细胞:
- 学习了 Swift 语言中的一些使用高阶函数的示例,包括
map
,reduce
,filter
等。 - 学习了利用高阶函数来构造以函数为主体的功能模块。
- 练习了一些奇怪的面试题。
Swift 烧脑体操(三) - 高阶函数的更多相关文章
- Swift 烧脑体操(二) - 函数的参数
前言 Swift 其实比 Objective-C 复杂很多,相对于出生于上世纪 80 年代的 Objective-C 来说,Swift 融入了大量新特性.这也使得我们学习掌握这门语言变得相对来说更加困 ...
- python高级 之(三) --- 高阶函数
高阶函数 map函数 简介 """ map(func,*iterables) 参数:一个是函数.一个是序列 作用:将序列中的元素依此作用于函数,将函数运行结果返回 存放于 ...
- JavaScript中的高阶函数
之前写的<JavaScript学习手册>,客户跟我说有些内容不适合初学者,让我删了,感觉挺可惜的,拿到这里和大家分享. JavaScript中的一切都是对象,这句话同样适用于函数.函数对象 ...
- Python 基础之匿名函数 迭代器和高阶函数
一.匿名函数 lambda表达式 用一句话来表达只有返回值的函数,叫匿名函数特点:简洁方便语法:lambda 参数: 返回值 1.不带有参数的lambda表达式 def func(): retu ...
- swift 的高阶函数的使用代码
//: Playground - noun: a place where people can play import UIKit var str = "Hello, playground& ...
- Swift 中的高阶函数和函数嵌套
高阶函数 在Swift中,函数可做为“一等公民”的存在,也就意味着,我们可以和使用 int 以及 String 一样,将函数当做 参数.值.类型来使用. 其中,将函数当作一个参数和值来使用可见下: t ...
- filter,map,reduce三个数组高阶函数的使用
filter ,map ,reduce三个高阶函数的使用 普通方法解决数据问题 const nums1= [10,20,111,222,444,40,50] // 需求1.取出小于100的数字 // ...
- 高阶函数---swift中的泛型介绍(一步步实现Map函数)
说明 本文内容均出自函数式 Swift一书, 此处整理仅仅是为了自己日后方便查看, 需要深入研究的话, 可以点进去购买, 支持原作者 本书由 王巍–新浪微博大神翻译 OneV's Den 喵神博客 接 ...
- python 高阶函数三 filter()和sorted()
一.filter()函数 filter()接收一个函数和一个序列.filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素. >>> ...
随机推荐
- 在线更新dede程序后 网站出现错误 DedeCMS Error:Tag disabled:"php" more...!
dedecms出现DedeCMS Error:Tag disabled:php原因解决 ------------------------------------------------------- ...
- php 列出当前目录
$path="."; //初使化用户所操作目录 $prevpath=dirname($path); //初使化当前脚本所在目录 $dir_handle=opendir($path) ...
- dubbo zookeeper案例
Alibaba有好几个分布式框架,主要有:进行远程调用(类似于RMI的这种远程调用)的(dubbo.hsf),jms消息服务(napoli.notify),KV数据库(tair)等.这个框架/工具/产 ...
- Android · SQLiteOpenHelper实例PrivateContactsDBHelper
package privatecontact; import android.content.ContentValues; import android.content.Context; import ...
- Centos6.0使用第三方YUM源(EPEL,RPMForge,RPMFusion)
yum是centos下很方便的rpm包管理工具,配置第三方软件库使你的软件库更加丰富.以下简单的讲下配置的步骤. 首先,需要安装yum-priorities插件: yum install yum-pr ...
- arcgis水文分析
前言 1.在开始之前首先需要注意几点: 1.arcgis 需要 python2.7 的支持,并有必要的模块库,请一定注意避免与其他软件冲突,例如tecplot 2009 需要python2.5的支持, ...
- matlab2016b-linux版本在ubutu16.04x64上面不能打开摄像头的处理方法
this can not work. need find other way. ================================================== ...
- JAVA中两个Set比较找出交集、差集、并集
当做到某些功能的时候,使用Set能够快速方便地将需要的类型以集合类型保存在一个变量中,Set是最简单的一种集合,集合中的对象不按特定的方式排序,并且没有重复对象. //两个Set比较找出交集.差集.并 ...
- Android NDK开发初步
在配置好NDK开发之后就能够使用C/C++开发android了.以下以一个HelloWorld项目来说明 1.新建一个Androidproject 新建一个HelloWorldproject 代码例如 ...
- H2 database 操作操作内存表
本例开发工具为 NetBeans,使用b2前提安装jdk. 第一步:在官网下载驱动包 :http://www.h2database.com ,本例版本为: h2-1.4.192.jar 第二步:安装开 ...