Scala学习总结
一、Scala简介
Scala特点:
- Scala 是一门多范式 (multi-paradigm) 的编程语言 ,设计初衷是要集成面向对象编程和函数式编程的各种 特性。
- Scala 是一门以 java 虚拟机 (JVM) 为运行环境的编程语言 ,Scala 源代码(.scala)会被编译成 Java 字节码(.class) ,然后运行于 JVM 之上 ,并可以调用现有的 Java 类库 ,实现两种语言的无缝对接。 强类型语言
- 简洁高效 (各种语法糖)
- 源于java ,与java对比学习 ,更易掌握
二、变量
1. 基本语法
先声明再使用:
val/var 变量名 [:变量类型] = 变量值
| 注意: 变量类型可以省略 ,变量类型确定后就无法改变 (强类型语言) var修饰的变量可以改变 ,val修饰的变量 不可变。 val是线程安全的 ,效率更高 ,val修饰的变量在编译后 ,等同于加上了final 。
变量声明时需要初始值。
2. 数据类型
Scala 与 Java 有着相同的数据类型 ,在 Scala 中数据类型都是对象 ,也就是说 scala 没有 java 中的原生类 型。 Scala 数据类型分为两大类 AnyVal(值类型) 和 AnyRef(引用类型) , 注意:不管是 AnyVal 还是 AnyRef 都是对象。
2.1. 数据类型一览图

说明:
1. Any 是所有类的根类型,即所有类的父类(基类)。
2. 在 scala 中类分为两个大的类型分支(AnyVal [值类型 ,即可以理解成就是 java 的基本数据类型], AnyRef 类型)。
3. 在 AnyVal 虽然叫值类型 ,但是仍然是类(对象)。
4. 在 scala 中有两个特别的类型(Null ), 还有一个是 Nothing。
5. Null 类型只有一个实例 null, 他是 bottom class ,是 AnyRef 的子类。
6. Nothing 类型是所有类的子类 , 它的价值是在于因为它是所有类的子类 ,就可以将 Nothing 类型的对象返回 给任意的变量或者方法 ,比如案例:
def f1():Nothing= {
//表示 f1 方法就是没有正常的返回值 ,专门用于返回异常
throw new Exception("异常发生")
}
| 7. 在 scala 中仍然遵守 低精度的数据自动的转成 高精度的数据类型。
8. 在 scala 中 , Unit 类型比较特殊 ,这个类型也只有一个实例 () 。
2.2. 整数类型
2.2.1 整数类型的分类
数据类型
| 描述
| Byte
| 8位有符号补码整数。数值区间为 -128 到 127
| Short
| 16位有符号补码整数。数值区间为 -32768 到 32767
| Int
| 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
| Long
| 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807
| 2.2.2 整型的使用细节
Scala 各整数类型有固定的表数范围和字段长度 ,不受具体 OS 的影响 ,以保证 Scala 程序的可移植性。
Scala 的整型 常量/字面量 默认为 Int 型 ,声明 Long 型 常量/字面量 须后加‘l’’或‘L’ 。
Scala 程序中变量常声明为 Int 型 ,除非不足以表示大数 ,才使用 Long。
2.3. 浮点类型
2.3.1 浮点类型的分类
数据类型
| 描述
| Float
| 32 位, IEEE 754 标准的单精度浮点数
| Double
| 64 位, IEEE 754 标准的双精度浮点数
| 2.3.2 浮点型使用细节
与整数类型类似 ,Scala 浮点类型也有固定的表数范围和字段长度 ,不受具体 OS 的影响。 Scala 的浮点型常量默认为 Double 型 ,声明 Float 型常量 ,须后加‘f’或‘F’。
通常情况下 ,应该使用 Double 型 ,因为它比 Float 型更精确(小数点后大致 7 位)。
2.4. 字符类型(Char)
字符常量是用单引号(‘ ’)括起来的单个字符。例如:var c1 = 'a‘ var c2 = '中‘ var c3 = '9' 2) Scala 也允许使用转 义字符‘\’来将其后的字符转变为特殊字符型常量。例如:var c3 = ‘\n’ // '\n' 表示换行符可以直接给 Char 赋一个整数 ,然后输出时 ,会按照对应的 unicode 字符输出 ['\u0061' 97] Char 类型是可以进行运算的 ,相当于一个整数 ,因为它都对应有 Unicode 码。
2.5. 布尔类型(Boolean)
布尔类型也叫 Boolean 类型 , Booolean 类型数据只允许取值 true 和 false
boolean 类型占 1 个字节。 boolean 类型适于逻辑运算 ,一般用于程序流程控制
2.6. Unit 类型、 Null 类型和 Nothing 类型
数据类型
| 描述
| Unit
| 表示无值 ,和其他语言中void等同。用作不返回任何结果的方法的结果类型。 Unit只有一个实 例值 ,写成()
| Null
| null 或空引用
| Nothing
| Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型
| 2.6.2 使用细节
Unit 类型用来标识过程 ,也就是没有明确返回值的函数。 由此可见 , Unit 类似于 Java 里的void。 Unit 只有 一个实例 ,() ,这个实例也没有实质的意义
Null 类只有一个实例对象 , null ,类似于 Java 中的 null 引用。 null 可以赋值给任意引用类型(AnyRef) ,但是 不能赋值给值类型(AnyVal: 比如 Int, Float, Char, Boolean, Long, Double, Byte, Short)
Nothing ,可以作为没有正常返回值的方法的返回类型 ,非常直观的告诉你这个方法不会正常返回 ,而且由于 Nothing 是其他任意类型的子类 ,他还能跟要求返回值的方法兼容。
二、运算符
1. 运算符介绍
运算符是一种特殊的符号 ,用以表示数据的运算、赋值和比较等。
算术运算符
赋值运算符
关系运算符
逻辑运算符
位运算符
2. 运算符一览表
2.1. 算术运算符
假定变量 A 为 10 , B 为 20:
运算符
| 描述
| 实例
| +
| 加号
| A + B 运算结果为 30
| -
| 减号
| A - B 运算结果为 -10
| *
| 乘号
| A * B 运算结果为 200
| /
| 除号
| B / A 运算结果为 2
| %
| 取余
| B % A 运算结果为 0
|
2.2. 赋值运算符
Scala 中没有++、--操作符 ,需要通过+=、-=来实现同样的效果
运算符
| 描述
| 实例
| =
| 简单的赋值运算 ,指定右边操作数赋值给左边的操作数。
| C = A + B 将 A + B 的运算结果赋 值给 C
| +=
| 相加后再赋值 ,将左右两边的操作数相加后再赋值给左边的 操作数。
| C += A 相当于 C = C + A
| -=
| 相减后再赋值 ,将左右两边的操作数相减后再赋值给左边的 操作数。
| C -= A 相当于 C = C - A
| *=
| 相乘后再赋值 ,将左右两边的操作数相乘后再赋值给左边的 操作数。
| C *= A 相当于 C = C * A
| /=
| 相除后再赋值 ,将左右两边的操作数相除后再赋值给左边的 操作数。
| C /= A 相当于 C = C / A
| %=
| 求余后再赋值 ,将左右两边的操作数求余后再赋值给左边的 操作数。
| C %= A is equivalent to C = C %
A
| > 2
| &=
| 按位与运算后赋值
| C &= 2 相当于 C = C & 2
| ^=
| 按位异或运算符后再赋值
| C ^= 2 相当于 C = C ^ 2
| |=
| 按位或运算后再赋值
| C |= 2 相当于 C = C | 2
| 2.3. 关系运算符
关系运算符的结果都是 Boolean 型 ,也就是要么是true ,要么是 false。关系运算符组成的表达式 ,我们称为关系 表达式。 如果两个浮点数进行比较 ,应当保证数据类型一致.
运算符
| 描述
| 实例
| ==
| 等于
| (A == B) 运算结果为 false
| !=
| 不等于
| (A != B) 运算结果为 true
| >
| 大于
| (A > B) 运算结果为 false
| </p/tdtdp小于/p/tdtdp(A < B) 运算结果为 true/p/td/trtrtdp>=
| 大于等于
| (A >= B) 运算结果为 false
| >
| 无符号右移
| 三、程序流程控制
1. if - else
Scala 中任意表达式都是有返回值的 ,也就意味着 if else 表达式其实是有返回结果的 ,具体返回结果的值取 决于满 足条件的代码体的最后一行内容。Scala 中是没有三元运算符 ,但是可以利用这个特性使用if--else进行三元运算。
例如:
val num = StdIn.redInt()
val res = if (num > 3) "比3大" else "比三小"
println(res)
| 1.1. 单分支
1.2. 双分支
if (条件表达式) {
执行代码块1
} else {
执行代码块2
}
| 1.3. 多分支
if (条件表达式) {
执行代码块1
} else if (条件表达式) {
执行代码块2
} else if (条件表达式) {
执行代码块3
} ...
| 1.4. 嵌套分支
if (条件表达式) {
if (条件表达式) {
执行代码块1
} else {
执行代码块2
}
}
| 2. for 循环
2.1. 范围数据循环方式
2.1.1 to方式
for(i < ‐ 1 to 3) { // 这里的 1 to 3 也可以是一个集合 前后闭合 (包括 1 和 3)
print(i + " ")
}
| 2.1.2 until方式
for(i < ‐ 1 until 3) { //i的取值是1 和 2 ,until表示 前闭后开 的范围
print(i + " ")
}
| 2.2. 循环守卫
循环守卫 ,即循环保护式。保护式为 true 则进入循环体内部 ,为 false 则跳过 ,类似于 continue。
for(i < ‐ 1 to 3 if i != 2) { //输出1 3
print(i + " ")
}
| 2.3. 引入变量
for(i < ‐ 1 to 3; j = 4 ‐ i) {//没有关键字 ,所以要加 ; 隔断逻辑
print(j + " ")
}
| 2.4. 嵌套循环
for(i < ‐ 1 to 3; j < ‐ 1 to 3) {
println(" i =" + i + " j = " + j)
}
// 等价于
// 在业务复杂时使用
for (i < ‐ 1 to 3) {
for (j < ‐1 to 3) {
println(" i =" + i + " j = " + j)
}
}
| 2.5. 循环返回值(yield)
将遍历过程中处理的结果返回到一个新的Vector集合中 ,使用yield关键字 ,yield可以写代码块。
val res = for(i < ‐ 1 to 10) yield i * 2
println(res)
| 2.6. 控制步长
for循环的步长控制有两种方法 ,通常使用循环守卫的方式。
例:遍历 1-10, 步长为 3
2.6.1 Range
Range是一个集合 ,括号里面三个数表示: 1: start , 10: end 遍历到 (end -1) ,3: 表示 step
for (i < ‐ Range(1,10,3)) { //遍历1 ‐ (10 ‐1),步长3 until
println("i=" + i)
}
| 2.6.2 使用守卫
for (i < ‐ 1 to 10 if i % 3 == 1 ) {
println("i=" + i)
}
| 3. while 循环
特点:
- while 循环是先判断再执行语句。
- 与 If 语句不同 ,While 语句本身没有值 ,即整个 While 语句的结果是 Unit 类型的()
- 因为while 中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量 ,而变量需要声明在 while 循环的外部 ,那么就等同于循环的内部对外部的变量造成了影响 ,所以不推荐使用 ,而是推荐使用for 循环。
语法:
while (循环条件) {
循环体(语句)
循环变量迭代
}
| 4. do..while 循环
循环变量初始化;
do{
循环体(语句)
循环变量迭代
} while(循环条件)
| 5. while 循环的中断
Scala 内置控制结构特地去掉了 break 和 continue ,是为了更好的适应函数化编程 ,推荐使用函数式的风格解决 break 和 contine 的功能 ,而不是一个关键词。
5.1. 使用breakable控制循环的中断
//使用前需要导包
import util.control.Breaks._
//将需要通过breakable控制的代码放到breakable的大括号中
//相当于break,跳出整个循环
breakable {
for (i < ‐ 1 to 10) {
if (i == 5) {
break()
}
println("i=" + i)
}
}
//相当于continue,跳出本次循环 ,继续执行下一次循环
for (i < ‐ 1 to 10) {
breakable {
if (i == 5) {
break()
}
println("i=" + i)
}
}
|
5.2. 使用if-else或循环守卫实现continue效果
//当i=4,5时跳过
for(i < ‐ 1 to 10){
if (i != 4 && i != 5) {
println("i=" + i)
}
}
for (i < ‐ 1 to 10 if (i != 4 && i != 5)) {
println("i=" + i)
}
| 四、函数式编程
1. 函数式编程介绍
函数式编程是一种"编程范式" (programming paradigm) 。它属于结构化编程的一种 ,主要思想是把运算过程尽 量写成一系列嵌套的函数调用。函数式编程中 ,将函数也当做数据类型 ,因此可以接受函数当作输入 (参数) 和输 出 (返回值) 。(增强了编程的粒度)
在 Scala 中 ,方法和函数几乎可以等同(比如他们的定义、使用、运行机制都一样的) ,只是函数的使用方式 更加的 灵活多样。当一段功能代码出现多次时 ,编程时 ,就可以将这段功能代码抽取出来 ,做成函数 ,供调用。
2. 函数/方法的定义
def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] {
语句 ... //完成某个功能
return 返回值
}
|
- 函数声明关键字为 def (definition)
- [参数名: 参数类型], ...:表示函数的输入(就是参数列表), 可以没有。 如果有 ,多个参数使用逗号间隔 函数中的语句:表示为了实现某一功能代码块
- 函数可以有返回值,也可以没有 返回值的形式:
- [: 返回值类型] = 表示有返回值 ,并且指定了返回值的类型
- 没写返回值类型只有等号, 表示返回值类型 ,使用类型推导
- 空的 ,表示没有返回值 ,即使有 return 也不生效
- 如果没有 return ,默认以执行到最后一行的结果作为返回值
3. 函数的调用机制

4. 函数的递归调用
一个函数/方法在函数/方法体内又调用了本身 ,我们称为递归调用。
例:
def test(n:Int){
if (n>2){
test(n ‐1)
} else {
println(s"n = $n")
}
}
// 输出n=2
| 5. 注意事项
- 函数的形参列表可以是多个, 如果函数没有形参 ,调用时 可以不带() 。
- 形参列表和返回值列表的数据类型可以是值类型和引用类型。
- Scala 中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下 ,return 关键字 可以省略。
- 因为 Scala 可以自行推断 ,所以在省略 return 关键字的场合 ,返回值类型也可以省略。
- 如果函数明确使用 return 关键字 ,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然 如果你什么都不写 ,即使有 return , 返回值为()。
- 如果函数明确声明无返回值 (声明 Unit) ,那么函数体中即使使用 return 关键字也不会有返回值。
- 如果明确函数无返回值或不确定返回值类型 ,那么返回值类型可以省略(或声明为 Any)。
- Scala 语法中任何的语法结构都可以嵌套其他语法结构(灵活) ,即 :函数/方法中可以再声明/定义函数/方法, 类中可以再声明类。
- Scala 函数的形参 ,在声明参数时 ,直接赋初始值(默认值) ,这时调用函数时 ,如果没有指定实参 ,则会使用 默认值。如果指定了实参 ,则实参会覆盖默认值。
- 如果存在多个参数 ,每一个参数都可以设定默认值 ,那么这个时候 ,传递的参数到底是覆盖默认值 ,还是赋 值给没有默认值的参数 ,就不确定了(默认按照声明顺序[从左到右])。在这种情况下 ,可以采用带名参数 。 scala 函数的形参默认是 val 的 ,因此不能在函数中进行修改。
- 递归函数未执行之前是无法推断出来结果类型 ,在使用时必须有明确的返回值类型。
- Scala 函数支持可变参数。
6. 过程 (procedure)
将函数的返回类型为 Unit 的函数称之为过程(procedure) ,如果明确函数没有返回值 ,那么等号可以省略。
例:
def f1(name: String): Unit = {
println(name + " hello ")
}
| 如果函数声明时没有返回值类型 ,但是有 = 号 ,可以进行类型推断最后一行代码。这时这个函数实际是有返回值的 ,该函数并不是过程。
7. 惰性函数
惰性计算 (尽可能延迟表达式求值) 是许多函数式编程语言的特性。惰性集合在需要时提供其元素 ,无需预先计算 它们 ,这带来了一些好处。首先 ,您可以将耗时的计算推迟到绝对需要的时候。其次 ,您可以创造无限个集合 ,只 要它们继续收到请求 ,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。
当函数返回值被声明为 lazy 时 ,函数的执行将被推迟 ,直到我们首次对此取值 ,该函数才会执行。这种函数我 们 称之为惰性函数 ,在 Java的某些框架代码中称之为懒加载(延迟加载)。
def main(args: Array[String]): Unit = {
lazy val res = sum(1,2)
println(" ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ")
println("res=" + res) //当需要使用到 res 时 ,就会真正的开始计算
}
def sum(n1:Int,n2:Int): Int = {
println("sum 被调用 ..")
n1 + n2
}
| 注意:
- lazy 不能修饰 var 类型的变量
- 不但是 在调用函数时 ,加了 lazy ,会导致函数的执行被推迟 ,我们在声明一个变量时 ,如果给声明了 lazy , 那么变量值得分配也会推迟。 比如 lazy val i = 10
8. 异常
Scala 提供 try 和 catch 块来处理异常。try 块用于包含可能出错的代码。catch 块用于处理 try 块中发生的异常。 可以根据需要在程序中有任意数量的 try...catch 块。语法处理上和 Java 类似 ,但是又不尽相同。
例:
object ScalaException {
def main(args: Array[String]): Unit = {
//scala 中去掉所谓的 checked (编译) 异常
//设计者认为 ,如果程序员编程时 ,认为某段代码可疑 ,就直接 try 并处理
//说明
//1. 如果代码可疑 ,使用 try 进行处理
//2. 在 catch 中 ,可以有多个 case ,对可能的异常进行匹配
//3. case ex: Exception => println("异常信息=" + ex.getMessage)
// (1) case 是一个关键字
// (2) ex: Exception 异常的种类
// (3) => 表明后的代码是对异常进行处理 ,如果处理的代码有多条语句可以{}扩起
//4. 在 scala 中把范围小的异常放在后面 ,语法不会报错 ,但是不推荐
//5. 如果捕获异常 ,代码即使出现异常 ,程序也不会崩溃。
try {
var res = 10 / 0
} catch {
case ex: ArithmeticException => {
println("算术异常=" + ex.getMessage)
println("111")
println("222")
}
case ex: Exception => println("异常信息=" + ex.getMessage)
} finally {
println("finaly 的代码 ...")
}
println("程序继续 ....")
}
}
| 注意:
- 我们将可疑代码封装在 try 块中。 在 try 块之后使用了一个 catch 处理程序来捕获异常。如果发生任何异常, catch 处理程序将处理它 ,程序将不会异常终止。
- Scala 的异常的工作机制和 Java 一样 ,但是 Scala 没有“checked(编译期)”异常 ,即 Scala 没有编译异常这个 概念 ,异常都是在运行的时候捕获处理。
- 用 throw 关键字 ,抛出一个异常对象。所有异常都是 Throwable 的子类型。throw 表达式是有类型的 ,就是 Nothing ,因为 Nothing 是所有类型的子类型 ,所以 throw 表达式可以用在需要类型的地方
例如:
def main(args: Array[String]): Unit = {
val res = test()
println(res.toString)
}
def test(): Nothing = {
throw new Exception("不对")
}
|
- 在 Scala 里 ,借用了模式匹配的思想来做异常的匹配 ,因此 ,在 catch 的代码里 ,是一系列 case 子句来匹配异常。当匹配上后 => 有多条语句可以换行写 ,类 似 java 的 switch case x: 代码块..
- 异常捕捉的机制与其他语言中一样 ,如果有异常发生 ,catch 子句是按次序捕捉的。 因此 ,在 catch 子句中,越具体的异常越要靠前 ,越普遍的异常越靠后 ,如果把越普遍的异常写在前 ,把具体的异常写在后 ,在 scala 中也不会报错(不报错 ,但是不推荐) ,但这样是非常不好的编程风格。
- finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤 ,一般用于对象的清理工作 ,这点和 Java 一样。
- Scala 提供了 throws 关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息。 它有助于调用函数处理并将该代码包含在 try-catch 块中 ,以避免程序异常终止。在 scala中 ,可以使用 throws 注释来声明异常 例如:
def main(args: Array[String]): Unit = {
f11()
}
@throws(classOf[NumberFormatException]) //等同于 NumberFormatException.class
def f11() = {
"abc".toInt
}
| 9. 匿名函数
没有名字的函数就是匿名函数 ,可以通过函数表达式 ,来设置匿名函数。
val triple = (x: Double) => 3 * x
pritnln(triple) // 类型
println(triple(3))
| 说明 :(x: Double) => 3 * x 就是匿名函数 (x: Double) 是形参列表 , => 是规定语法表示后面是函数体 , 3 * x 就是函数体 ,如果有多行 ,可以 {} 换 行写.triple 是指向匿名函数的变量。
案例:
object NoNameFunction {
def main(args: Array[String]): Unit = {
//编写一个匿名函数 ,可以返回 2 个整数的和 ,并输出该匿名函数的类型
//如果我们定义一个函数 ,则变量名字要写
val f1 = (x1:Int,x2:Int) => {
x1 + x2
}
println(f1(10, 30)) // 40
println(f1) //
//调用 f2 完成一个运算
println(f2(f1,30,40)) // 70 // f = f1
}
//方法 ,可以接受一个函数 ,该函数返回两个数的差
//这时 ,我们只是写一个函数的格式(签名)
def f2(f:(Int,Int) => Int, n1:Int,n2:Int): Int = {
f(n1,n2)
}
}
| 10. 高阶函数
能够接受函数作为参数的函数 ,叫做高阶函数 (higher-order function)。可使应用程序更加健壮。 高阶函数可 以 返回一个匿名函数。
10.1. 高阶函数的基本使用
def test(f: Double => Double, n1: Double) = {
f(n1) //调用 f 函数
}
//
def sum(d: Double): Double = {
d + d
}
val res = test(sum, 6.0)
println("res=" + res)
def minusxy(x: Int) = {
(y: Int) => x – y // 函数表达式 , 返回的是一个匿名函数
}
//说明
//minusxy 高阶函数 返回的是 (y: Int) => x – y 匿名函数
//minusxy(3) => 返回的就是一个具体的匿名函数 (y: Int) => 3 – y
val result3 = minusxy(3)(5)
println(result3)
//minusxy(3)(5) => 3 – 5 = ‐2
|
11. 参数(类型)推断
- 参数类型是可以推断时 ,可以省略参数类型
- 当传入的函数 ,只有单个参数时 ,可以省去括号
- 如果变量只在=>右边只出现一次 ,可以用_来代替
12. 闭包
如果一个函数 ,访问到了它的外部 (局部) 变量的值 ,那么这个函数和他所处的环境 ,称为闭包。
def minusxy(x: Int) = (y: Int) => x – y
//说明
//1. minusxy 返回了 (y: Int) => x – y 匿名函数
//2. 使用到 x 值 ,x 是它引用到的一个环境变量
//3. 匿名函数和 x 组合成一个整体 ,构成了一个闭包
//4. f 就是一个闭包
val f = minusxy(20)
println("f(1)=" + f(1)) // 19
println("f(2)=" + f(2)) // 18
| 12.1 定义
在计算机科学中 ,闭包 (英语:Closure) ,又称词法闭包 ( Lexical Closure) 或函数闭包 (function closures) ,是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体 ,它存 储了一个函数 (通常是其入口地址) 和一个关联的环境 (相当于一个符号查找表) 。环境里是若干对符号和 值的对应关系 ,它既要包括约束变量 (该函数内部绑定的符号) ,也要包括自由变量 (在函数外部定义但在 函数内被引用) ,有些函数也可能没有自由变量。闭包跟函数最大的不同在于 ,当捕捉闭包的时候 ,它的自 由变量会在捕捉时被确定 ,这样即便脱离了捕捉时的上下文 ,它也能照常运行。捕捉时对于值的处理可以是 值拷贝 ,也可以是名称引用 ,这通常由语言设计者决定 ,也可能由用户自行指定 (如C++) 。
因为外层调用结束返回内层函数后 ,经过堆栈调整(比如在C中主调或者被调清理) ,外层函数的参数已经被释放了 ,所以内层是获取不到外层的函数参数的。为了能够将环境 (函数中用到的并非该函数参数的变量和他 们的值) 保存下来 (需要考虑释放问题 ,可以通过GC可以通过对象生命周期控制 ,GC是一个常见选择) ,这 时会将执行的环境打一个包保存到堆里面。
13. 函数柯里化 (Currying)
将一个参数列表的多个参数 ,变成多个参数列表的过程。也就是将普通多参数函数变成高阶函数的过程。
13.1 定义
在计算机科学中 ,柯里化 (英语:Currying) ,又译为卡瑞化或加里化 ,是把接受多个参数的函数变换成接 受一个单一参数 (最初函数的第一个参数) 的函数 ,并且返回接受余下的参数而且返回结果的新函数的技 术。在直觉上 ,柯里化声称“如果你固定某些参数 ,你将得到接受余下参数的一个函数”。柯里化是一种处理 函数中附有多个参数的方法 ,并在只允许单一参数的框架中使用这些函数。
13.2 scala中的柯里化函数
// Currying
def add(a: Int)(b: Int): Int = a + b
println(add(4)(3))
val addFour = add(4) _
// val addFour: Int => int = add(4)
println(addFour(3))
| 14. 控制抽象
值调用:按值传递参数 ,计算值后再传递。多数语言中一般函数调用都是这个方式 ,C++还存在引用传递。
名调用:按名称传递参数 ,直接用实参替换函数中使用形参的地方。能想到的只有C语言中的带参宏函数 ,其 实并不是函数调用 ,预处理时直接替换。 例子:
// pass by value
def f0(a: Int): Unit = {
println("a: " + a)
println("a: " + a)
}
f0(10)
// pass by name, argument can be a code block that return to Int
def f1(a: => Int): Unit = {
println("a: " + a)
println("a: " + a)
}
def f2(): Int = {
println("call f2()")
10
}
f1(10)
f1(f2()) // pass by name, just replace a with f2(), then will call f2() twice
f1({
println("code block") // print twice
30
})
| 应用:使用传名参数实现一个函数相当于while的功能。
// built ‐in while
var n = 10
while (n >= 1) {
print(s"$n ")
n-= 1
}
println()
// application: self ‐defined while, implement a function just like while keyword
def myWhile(condition: => Boolean): (=> Unit) => Unit = {
def doLoop(op: => Unit): Unit = {
if (condition) {
op
myWhile(condition)(op)
}
}
doLoop _
}
n= 10
myWhile (n >= 1) {
print(s"$n ")
n ‐= 1
}
println()
// simplfy
def myWhile2(condition: => Boolean): (=> Unit) => Unit = {
op => {
if (condition) {
op
myWhile2(condition)(op)
}
}
}
n= 10
myWhile (n >= 1) {
print(s"$n ")
n ‐= 1
}
println()
// use currying
def myWhile3(condition: => Boolean)(op: => Unit): Unit = {
if (condition) {
op
myWhile3(condition)(op)
}
}
n= 10
myWhile3 (n >= 1) {
print(s"$n ")
n ‐= 1
}
println()
|
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |