Scala函数式编程
1、函数基础
Scala 语言是一个完全函数式编程语言。万物皆函数。
函数的本质:函数可以当做一个值进行传递
1.1、函数基本语法
1.2、函数参数
1.2.1、可变参数
语法:在所定义的参数类型后面加上一个*即可
例:
object Demo2 {
def main(args: Array[String]): Unit = {
// 定义一个打印多个字符串的函数
def printStr(str: String*): Unit = {
for (elem <- str) {
println(elem)
}
}
// 函数调用
printStr("Hello")
printStr("Hello", "Scala")
}
}
1.2.2、默认参数与带名参数
默认参数:
语法:在定义函数时给参数一个默认值
带名参数:
语法:在函数调用时给定参数的名字,可以使传入参数的顺序不同
例:
object Demo3 {
def main(args: Array[String]): Unit = {
// 打印信息
def info(id: Int = 1001, name: String, age: Int = 20): Unit = {
println(s"id:$id,name:$name,age:$age")
}
// 使用默认参数id:1001, age:20
info(name = "张三") //id:1001,name:张三,age:20
// 没有使用默认参数
info(1002, "李四", 18) //id:1002,name:李四,age:18
// 使用带名参数,可以不按顺序传入参数
info(name = "王五", id = 1003, age = 19) //id:1003,name:王五,age:19
}
}
1.3、函数至简原则
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
- 如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
例:
object Demo4 {
def main(args: Array[String]): Unit = {
// 完整写法,求一个数的平方
def square1(i: Int): Int = {
return i * i
}
// 省略return Scala默认最后一行为返回值
def square2(i: Int): Int = {
i * i
}
// 如果函数体只有一条语句,花括号可以省略
def square3(i: Int): Int = i * i
// 如果返回值类型可以推断出,即可省略
def square4(i: Int) = i * i
// 如果函数无参数,圆括号即可省略,下面函数返回一个张三字符串
def getName = "张三"
// 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
val f = (name: String) => println(name)
f("李四") //输出李四
}
}
2、函数高级
2.1、高阶函数
- 函数可以作为值进行传递
- 函数可以作为参数进行传递
- 函数可以作为函数返回值返回
例:
object Demo5 {
def main(args: Array[String]): Unit = {
// 函数作为值传递
val f = () => println("function1")
f() // 等号右边就是一个函数,f调用函数打印结果 :function1
// 函数作为参数传递
// 定义一个求和函数
def sum(x: Int, y: Int): Int = x + y
// 参数为一个函数的函数rs
def rs(f: (Int, Int) => Int): Int = {
f(10, 20)
}
println(rs(sum)) // 30
// 函数作为返回值
def fun1(a: Int): Int => Double = {
def fun2(b: Int): Double = {
Math.pow(a, b)
}
// 返回函数fun2
fun2
}
// 调用fun1返回一个函数
val tmp: Int => Double = fun1(2)
println(tmp(3)) //8.0 等同于求2的三次方
// 上述函数简化, 函数柯里化
def fun3(a: Int)(b: Int): Double = {
Math.pow(a, b)
}
println(fun3(2)(3))// 8.0
}
}
2.2、匿名函数
2.2.1、匿名函数的至简原则
- 参数的类型可以省略,会根据形参进行自动的推导
- 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
- 匿名函数如果只有一行,则大括号也可以省略
- 如果参数只出现一次,则参数省略且后面参数可以用_代替
2.2.1.1、传递一个参数
例:
object Demo6 {
def main(args: Array[String]): Unit = {
// 创建一个list
val list: List[Int] = List[Int](1, 2, 4, 5, 8)
// 使用匿名函数将list里的元素扩大两倍,完整写法
val str1: String = list.map((i: Int) => {
i * 2
}).mkString(",")
println(str1)
// 省略匿名函数的参数类型
val str2: String = list.map(i => {
i * 2
}).mkString(",")
println(str2)
// 当匿名函数只有一行函数体时,可以省略花括号
val str3: String = list.map(i => i * 2).mkString(",")
println(str3)
// 当匿名函数参数只有一个的时候,可以使用_代替
val str4: String = list.map(_ * 2).mkString(",")
println(str4)
}
}
2.2.1.2、传递两个参数
例:
object Demo7 {
def main(args: Array[String]): Unit = {
// 定义一个函数实现加减乘除
def fun(a: Int, b: Int, f: (Int, Int) => Int): Int = {
f(a, b)
}
// 加法
println(fun(9, 3, (x, y) => x + y)) //12
// 减法
println(fun(9, 3, (x, y) => x - y)) //6
// 乘法
println(fun(9, 3, (x, y) => x * y)) //27
// 除法
println(fun(9, 3, (x, y) => x / y)) //3
// 加法完整写法
println(fun(9, 3, (x: Int, y: Int) => {x + y})) //12
// 如果只有一行函数体,花括号可以省略
println(fun(9, 3, (x: Int, y: Int) => x + y)) //12
// 函数参数类型可以省略
println(fun(9, 3, (x, y) => x + y)) //12
// 如果参数只出现一次,参数可以使用_代替
println(fun(9, 3, _ + _)) //12
}
}
2.3、闭包
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
以下为闭包的使用
例:求a的b次方
object Demo8 {
def main(args: Array[String]): Unit = {
// 定义一个fun1函数求a的b次方
// 由于fun2中使用到了外层函数的数据,所以使用了闭包
def fun1(b: Int): Int => Int = {
def fun2(a: Int): Int = {
Math.pow(a, b).toInt
}
fun2
}
// 首先先固定一个参数,用来求任意数的2次方
val powBy2: Int => Int = fun1(2)
println(powBy2(3)) //9
println(powBy2(5)) //25
println(powBy2(8)) //64
// 同理也可以,先固定幂为3,求立方
val powBy3: Int => Int = fun1(3)
println(powBy3(2)) //8
println(powBy3(3)) //27
println(powBy3(4)) //64
}
}
2.4、柯里化
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
例:
object Demo10 {
def main(args: Array[String]): Unit = {
// 求b的a次方
def fun1(a: Int): Int => Int = {
def fun2(b: Int): Int = {
Math.pow(b, a).toInt
}
fun2
}
// 可以将上述函数简化成如下
// 简化写法
def fun3(a: Int)(b: Int): Int = {
Math.pow(b, a).toInt
}
// 使用方法 求3的平方
println(fun1(2)(3)) //9
println(fun3(2)(3)) //9
}
}
2.5、尾递归
递归函数指的是在函数中调用自身的函数。根据递归的调用方式,可以将递归分为首递归和尾递归。
- 在首递归中,函数调用自身之后,再进行其他运算,因此在运行的时候需要不断的使用新的栈帧来保存函数中的临时变量,这种情况下,当递归的层次不是很多的时候,还不会出现问题,但是一旦敌对的层次很深的时候就很容易出现stack overflow的情况,这对程序而言往往是致命的。因此使用首递归并不安全。
- 而尾递归则完全不同,在尾递归函数中, 所有的计算都在调用之前完成(这一点非常重要), 因此函数可以在调用完成之后释放栈帧,因此不管递归的层次有多深都不会发生stack overflow的情况。
例:
object Demo9 {
def main(args: Array[String]): Unit = {
// 普通递归函数计算阶乘
println(fact(5)) //120
// 尾递归方式实现计算阶乘
println(fact2(5)) //120
// 普通递归实现计算阶乘
def fact(n: Int): Int = {
if (n == 1) return 1
fact(n - 1) * n
}
// 尾递归实现计算阶乘
def fact2(n: Int): Int = {
// 类似实现循环,将计算结果保存到参数的位置
def loop(n: Int, res: Int): Int = {
if (n == 1) return res
loop(n - 1, res * n)
}
loop(n, 1)
}
}
}