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()

 

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/zb-7071/p/17381783.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!