6-函数和闭包-Scala

前言

本节将介绍Scala中的函数和闭等相关知识。
环境:
Windows + Scala-2.12.8
代码见GitHub:

1. 方法

定义函数的最常用的方式是作为某个对象的成员,这样的函数被称为方法。下面给出一个示例,打印超出指定长度的行。
文件:8.1.scala - 注:该文件属于完整的Scala应用程序(main入口),为了简单操作,依然使用执行脚本的方式
内容包括中文会报错。

// println line which over width
import scala.io.Source
object LongLines {
  def processFile(filename: String, width: Int) = {
    val source = Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(filename, width, line)
  }
  private def processLine(filename: String,
    width: Int, line: String) = {
    if (line.length > width)
      println(filename + ": " + line.trim)
  }
}

object FindLongLines {
  def main(args: Array[String]) = {
    val width = args(0).toInt
    for (arg <- args.drop(1))
      LongLines.processFile(arg, width)
  }
}

以脚本的方式执行:
在这里插入图片描述

2. 局部函数

在Scala中,局部函数就和局部变量一样,只允许在定义该局部函数的函数中使用,外部无法使用。并且,局部函数可以使用外层函数的类参数。
修改上面的例子:
文件:8.2

// Local function
import scala.io.Source
object LongLines {
  def processFile(filename: String, width: Int) = {
    def processLine(line: String) = {
      if (line.length > width)
        println(filename + ": " + line.trim)
    }
    val source = Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(line)
  }
}

object FindLongLines {
  def main(args: Array[String]) = {
    val width = args(0).toInt
    for (arg <- args.drop(1))
      LongLines.processFile(arg, width)
  }
}

在这里插入图片描述

3. 一等函数

Scala支持一等函数。不仅可以定义函数并调用它们,还可以使用匿名的字面量来编写函数并将它们作为值传递。
函数字面量被编译成类,并在运行时实例化成函数值。因此,函数字面量和函数值的区别在于,函数字面量存在于源代码,函数值以对象的形式存在于运行时。
函数字面量示例:

(x: Int) => x + 1

函数值示例(increase就是函数值):

var increase = (x: Int) => x + 1

在这里插入图片描述
另外,如果想要在函数字面量中包含多条语句,可以使用花括号,每条语句占一行:
在这里插入图片描述
再如,Scala很多类库都支持使用函数字面量,如foreach 方法支持传入一个函数作为实参:
在这里插入图片描述

4. 函数字面量的简写形式

Scala中提供了多个省去冗余信息,更加简要的编写函数的方式。
略去参数类型声明,同时根据Scala的自动类型推断省去括号。改写foreach :
在这里插入图片描述

5. 占位符语法

为了让函数字面量更加精简,还可以使用下划线作为占位符,用来表示一个或多个参数,只要满足每个参数字面量中出现一次即可。例如:
在这里插入图片描述
有时候当你用下划线为占位符时,编译器可能没有足够多的信息来推断缺失的类型,如:
在这里插入图片描述
不过,你可以给出类型,如:
v
多个下划线意味着多个参数。

6. 部分应用的函数

当你使用下划线时,实际上是在编写一个部分应用的函数。在Scala中,当你调用某个函数,传入任何需要的参数时,你实际上是应用那个函数到这些参数上。
如平常的做法:
在这里插入图片描述
部分应用的函数是一个表达式,在这个表达式中,并不给出函数需要的所有参数,而是给出部分或不给。
如,基础sum 创建一个部分应用的函数:
在这里插入图片描述
上述代码的过程:变量a 指向一个函数值对象。这个函数值是一个从Scala 编译器自动从sum _ 这个部分应用函数表达式生成的类的实例。由编译器生成的这个类有一个接收三个参数的apply 方法。生成的类的apply 方法之所以接收三个参数,是因为表达式sum _ 缺失的参数个数为3。编译器将a(1, 2, 3) 翻译成对函数值的apply方法的调用,即a.apply(1, 2, 3):
在这里插入图片描述
部分应用函数之所以叫部分应用函数是因为如sum _ 表示并没有把函数应用到所有的入参。不过,我们可以写一个只有一个参数缺失:
在这里插入图片描述
在某些地方,你甚至可以省去下划线,如:
在这里插入图片描述
这种方式只在明确需要函数的地方被允许,比如在一个不需要函数的地方:
在这里插入图片描述

7. 闭包

到目前为止,所有的函数字面量都只是引用了传入的参数。如:(x: Int) => x + 1,唯一用到的变量是x,不过也可以引用其他地方的变量,称为闭包:
其中 x 称为绑定变量,more称为自由变量。

(x: Int) => x + more

这个函数将more 也作为入参。如:
在这里插入图片描述
你可以发现,改变more 后,同时改变了add 方法引用more的值。因为,闭包捕获的是变量本身,而不是变量引用的值。即闭包能够看到闭包之外对more 的修改。同理,闭包对捕获的变量的修改也能在闭包外看到,如:
在这里插入图片描述
闭包之外的sum 可以看到闭包对sum 的改变。

8. 特殊的函数调用形式

到目前为止,大多数函数都是固定数量的形参,不过Scala 也支持其他形式的参数。

  1. 重复参数
    简单理解为传入一个可变长度的参数列表。需要在参数类型之后叫上 * :
    在这里插入图片描述
    args 的类型实际是Array[String]。不过,你不能传递一个Array[String]类型的实参。
    在这里插入图片描述
    解决办法:
    在这里插入图片描述

  2. 带名字的参数
    与平常的定义一样,你可能习惯这样做:
    在这里插入图片描述
    但是,另一种调用方法是在每个实参前加上参数名和等号,这样就可以改变顺序:
    在这里插入图片描述

  3. 缺省参数
    缺省参数即你可以在实参中不给出值,但意味着你需要在函数的定义出给它一个默认值,如:
    8.3.scala

// default parameter
def printTime(out: java.io.PrintStream = Console.out) = 
  out.println("time = " + System.currentTimeMillis())
printTime()

在这里插入图片描述

另一个示例:
8.4.scala

// default parameter
def printTime(out: java.io.PrintStream = Console.out,
  divisor: Int = 1) = 
  out.println("time = " + System.currentTimeMillis() / divisor)
printTime()
printTime(divisor = 1000)

在这里插入图片描述

9. 尾递归

尾递归就是一个递归函数,但函数的最后必须是调用自己的函数(单纯调用自己,比如下面有斐波那锲的测试)
例如一个尾递归函数:
在这里插入图片描述
非尾递归函数:
在这里插入图片描述
可以看出,尾递归函数抛出异常时只有1个栈帧,非尾递归函数有4个栈帧。是因为,尾递归函数是优化后的递归函数,如果你了解过递归函数会知道,递归函数会开启多个栈(在该层之下,再开启一层栈,类似于下楼),直到最后一个栈计算完后,才会一层一层的返回上层栈(类似于上楼)。而在Scala中,Scala编译器会优化尾递归函数,具体为检测到尾递归并将它跳转到函数的开始,并在跳转前将参数更新为新的值(即不开启新的栈)。

想到这里,猜测斐波那契数列是否是尾递归函数:
在这里插入图片描述
结果显示,Scala编译器并不认为它是尾递归函数。

后续篇章

7-控制抽象-Scala

完!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值