Scala学习总结(一)
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 |
<<= |
按位左移后再赋值 |
C <<= 2 相当于 C = C << 2 |
>>= |
按位右移后再赋值 |
C >>= 2 相当于 C = C >> 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 |
< |
小于 |
(A < B) 运算结果为 true |
>= |
大于等于 |
(A >= B) 运算结果为 false |
<= |
小于等于 |
(A <= B) 运算结果为 true |
2.4. 逻辑运算符
假定变量 A 为 1 , B 为 0:
运算符 |
描述 |
实例 |
&& |
逻辑与 |
(A && B) 运算结果为 false |
| | |
逻辑或 |
(A | | B) 运算结果为 true |
! |
逻辑非 |
!(A && B) 运算结果为 true |
2.5. 位运算符
位运算符用来对二进制位进行操作
运算符 |
描述 |
& |
按位与运算符 |
| |
按位或运算符 |
^ |
按位异或运算符 |
~ |
按位取反运算符 |
<< |
左移动运算符 |
>> |
右移动运算符 |
>>> |
无符号右移 |
三、程序流程控制
1. if - else
Scala 中任意表达式都是有返回值的 ,也就意味着 if else 表达式其实是有返回结果的 ,具体返回结果的值取 决于满 足条件的代码体的最后一行内容。Scala 中是没有三元运算符 ,但是可以利用这个特性使用if--else进行三元运算。
例如:
val num = StdIn.redInt() val res = if (num > 3) "比3大" else "比三小" println(res) |
1.1. 单分支
if (条件表达式) { 执行代码块 } |
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) // <function2> //调用 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() |