《scala 编程(第3版)》学习笔记2

第7章 内建的控制结构
  1. Scal所有控制结构都会返回某种值。P114
  2. if语句:
if(布尔表达式 1){
   // 如果布尔表达式 1 为 true 则执行该语句块
}else if(布尔表达式 2){
   // 如果布尔表达式 2 为 true 则执行该语句块
}else {
   // 如果以上条件都为 false 执行该语句块
}
  1. if子句中的返回值类型必须一致或为继承关系(最终返回父类类型)。P215
  2. while语句,返回单元值Unit,写做()P117:
//while
while(condition)
{
   statement(s);
}
//do...while
do {
   statement(s);
} while( condition );
  1. 赋值语句也返回单元值。如a='1'返回(),单元值和所有值用==比较都返回true。P118
var a=1
() == (a=3)
<console>:13: warning: comparing values of types Unit and Unit using `==' will always yield true
       () == (a=3)
          ^
res4: Boolean = true
//try表达式能返回子句值
val k=try{val c=1 }
k: Unit = ()

  1. for语句知识点。
    • 生成器:i<- 集合 叫生成器P120
    • 过滤器:可包含多个过滤器,过滤器由 if condition(i)组成。P121
    • 多个生成器一起使用,可得到嵌套循环。P122
    • 中途变量,使用和val一样。P123
    • 可用{}代替(),实现自动;推断,不然()要手工用;来断句。P122
    • if过滤器前的;可省略(很奇怪)。如 for(a<- arr if a>1)
    • yield产生新的集合,元素类型由yield后的代码体自有确定。for 子句 yield 代码体。P124
    • 完整子句包含生成器;定义(中途变量);过滤器。用{}可不写分号。P483
for{
a<- arr1 
m1=a+10
if a>1
b<- arr2 
m2=b+10
if b<2 
} yield {
(a,m1,b,m2)
}
//用括号
for(
a<- arr1; m1=a+10; if a>1;
b<- arr2; m2=b+10; if b<2 ;
) yield {
(a,m1,b,m2)
}
//推荐-用大括号-自动加分号功能只能在最后加
for{
a<- arr1; m1=a+10; if a>1
b<- arr2; m2=b+10; if b<2
} yield {
(a,m1,b,m2)
}
  1. 异常捕获:
    • scala异常清单
    • throw 表达式会返回Nothing。P126
    • try-catch返回值:无异常,返回try子句结果;有异常且被捕获,返回catch子句结果;有异常没被捕获,则无结果,因为异常向上扩散,该处无法继续执行下去。P128
    • 正常情况finally返回的值会被丢弃,除非显示的在finnally里写了return。则会覆盖try-catch的返回值。P129
    • 捕获所有异常
      try {
         val f = new FileReader("input.txt")
      } catch {
         case ex: FileNotFoundException =>{
            println("Missing file exception")
         }
         case ex: Execpion=> {
            println("通常这个Exception就能捕获几乎所有异常了")
         }
      } finally {
      println("做些副作用操作,如清理数据,关闭文件等")
      //通常不要return value
      }
"""
Exception is programmatically recoverable. Its subclass RuntimeException indicates a programming error and is usually not to be caught as well. Throwable is super class of Exception as well as Error. In normal cases we should always catch sub-classes of Exception, so that the root cause doesn't get lost.

如果下面的写法捕获不到异常,可以使用case e:Throwable尝试捕获所有异常

catch {
  case e:Exception => { 
    e.printStackTrace()
  }
}
 

Thowable catches really everything even ThreadDeath which gets thrown by default to stop a thread from the now deprecated Thread.stop() method. So by catching Throwable you can be sure that you'll never leave the try block without at least going through your catch block, but you should be prepared to also handle OutOfMemoryError and InternalError or StackOverflowError.

Catching Throwable is most useful for outer server loops that delegate all sorts of requests to outside code but may itself never terminate to keep the service alive.
"""
  1. match表达式:
    • break隐含。P130
    • 返回匹配值后面的子句值。P130
    • 通配符(_)用于表示任意值。P130
    • 如果各子句值类型不一样,返回的是各子句值的父类型(很可能是Any)
    • 多个条件在一个case中,可用|分开
it match{
	case "a" => dosometing1()
	case "b" => dosometing2()
	case 123 | "str1" => "ok"
	case _ => dosometing3()
	}
  1. 一定要用break时,也可break foreach函数。
// 导入以下包
import scala.util.control._

// 创建 Breaks 对象
val loop = new Breaks;

// 在 breakable 中循环
loop.breakable{
    // 循环
    for(...){
       ....
       // 循环中断
       loop.break;
   }
	arr.foreach{
	 loop.break;
	}
}
  1. 花括号会引入一个新的作用域。P136
  2. 没有副作用的函数,更容易进行单元测试。P138
第8章 函数与闭包
  1. 对象内的函数叫方法。P140
  2. 函数式编程设计原则是尽量将程序分解成多个小函数,每个小函数完成特定功能。我们称这些小函数为助手函数。P142
  3. 助手函数会污染命名空间。解决方法1是用私有函数。方法2是用局部函数。P143
def func1(a,..)={
	def local_func1(b,..)={a}
}

  1. private修饰符只用在类成员上,局部函数前不需要。P143
  2. 局部函数可直接访问父函数的参数。P144
  3. 函数字面量在运行时,被实例化成函数值。函数字面量支持多条语句,用{}即可。P144
(x:Int)=>{
...
}
  1. 函数字面量在实例化时,会实例化成FunctionN特质的类。N表示参数个数。P144
  2. 下划线"_"占位符,在函数字面量中可用来表示多个参数。只有当参数在函数字面量中仅出现一次时才能使用。第一个下划线表示第一个参数,第二个表示第二个参数,依此类推。当无法自动推断类型时,需手动标注类型。P148
a.filter(x=>x>0) 
a.filter(_>0) //等价于
val f = (_:Int)+(_:Int)
  1. 部分应用函数:
    • Scala中的偏函数(patial function)与部分应用函数(partial applied function)是不同的概念,偏函数(patial function)在模式匹配一章有介绍,简而言之,偏函数是在某些值没有定义的函数;部分应用函数是一个函数有N个参数,我们固定某些参数(或不固定而是替换整个参数列表),提供小于N个参数的函数。
    • 部分应用函数是一个表达式,这个表达式中部给出函数需要的所有参数,只给出部分或完全不给。部分应用函数返回的是一个函数,如sum _ 或sum(1,_: Int,2)
    • “_”也可替换整个参数列表。P149
    • 注意理解,为何a.foreach(println)可这么精简。博主认为不用从部分应用函数角度理解,因为在需要函数作为参数的地方,就可以直接填一个函数。P151
scala> def sum= (_:Int) + (_:Int) + (_:Int)
sum: (Int, Int, Int) => Int

scala> def sum1(a:Int,b:Int,c:Int)=a+b+c
sum1: (a: Int, b: Int, c: Int)Int

//注意,定义函数时,不要用部分应用函数,不然相当于包一层sum()
scala> val sum2= (_:Int) + (_:Int) + (_:Int)
sum: (Int, Int, Int) => Int = <function3>


scala> val a =sum _
a: () => (Int, Int, Int) => Int = <function0>

//注意这个差异
scala> val a1=sum1  _
a1: (Int, Int, Int) => Int = <function3>

scala> a(1,2,3)
<console>:14: error: too many arguments for method apply: ()(Int, Int, Int) => Int in trait Function0
       a(1,2,3)
        ^

scala> a()(1,2,3)
res4: Int = 6

scala> a1(1,2,3)
res5: Int = 6


//以下,sum和sum1表现一致
scala> val b= sum(1,_:Int,3)
b: Int => Int = <function1>

scala> b(2)
res3: Int = 6

scala> val c=sum(1,_)
                  ^
<console>:12: error: not enough arguments for method apply: (v1: Int, v2: Int, v3: Int)Int in trait Function3.
Unspecified value parameter v3.
       val c=sum(1,_)
                ^

scala> val c=sum(1,_:Int,_:Int)
c: (Int, Int) => Int = <function2>

scala> val d=sum(_:Int,2,_:Int)
d: (Int, Int) => Int = <function2>
  1. 函数作为一等的,只有在明确需要函数作为参数的地方可以直接用函数名func,其他地方都要写成 func _ 或者 func(_)的形式,因为这样返回的是一个部分应用函数。P151
  2. 闭包:
    • 运行时,会引入自有变量的函数字面量创建出来的函数值,叫做闭包。P154
    • 闭包能感知自由变量的变化,对自由变量的改动也会反应到闭包外。P154
    • 闭包引用的自由变量的值,为闭包被创建时变量活跃的值。P155
    • 被返回的闭包,会使得自由变量在堆上继续存活。P155
    • 博主认为,直接用函数的方式,也能实现闭包。见如下代码。
var more=3
//闭包
val f=(x:Int)=> x+more

f(1) //4
more=10
f(1) //11

//函数方式
var more=3
def f4(x:Int)= x+more
f4(1) //4
more=10
f4(1) //11


//闭包引用的自由变量的值,为闭包被创建时变量活跃的值。
def f2(more:Int)= (x:Int)=> x+more //该函数每调用一次,就会返回一个闭包
val c1=f2(1)
val c999=f2(999)

c1(1) //2
c999(1) //1000

//局部函数方式
def f3(more:Int)={
    def closure(x:Int)={
        x+more
    }
    closure(_) //只写closure会报错,因为只有明确需要一个函数参数的地方,参能只写函数。
//Unapplied methods are only converted to functions when a function type is expected.
//You can make this conversion explicit by writing `closure _` or `closure(_)` instead of `closure`.
}
val c1=f3(1)
val c999=f3(999)

c1(1) //2
c999(1) //1000
  1. 重复类型参数。类型后加*,如下示例。实际上再func函数内部,args的类型是Array[T]。但又不能直接把Array类型的变量,如arr当做参数给到函数,会报错。应该要 func(arr:_*)解开。
def func(args:String*)={
	...
}

  1. 尾递归。只有在最后一步调用自己的函数,才能享受scala的尾递归优化。优化位类似使用了while的优化,使得所有调用都在同一个栈帧中执行,不会像其他语言每次调用都构建一个新的栈帧。P160
//如下两个函数,在编译后,字节码相同。
def approximate(gusess:Double):Double={
	if(isGoodEnough(guess)) guess
	else approximate(improve(guess))
}

def approximateLoop(initialGuess:Double):Double={
	var guess=initialGuess
	while(!isGoodEnough(guess))
		guess=improve(guess)
	guess
}

第9章 减少代码重复

  1. 利用高阶函数(即接收函数作为参数的函数)减少代码。P164
  2. 函数参数的表示法:
func:(Sting,String)=>Int //有参数
func: () => Int //无参数
func: => Int // 传名函数
  1. 函数柯里化func(a:Int,b:Int)->func(a:Int)(b:Int) 柯里化实际是做了两次传统函数调用。P171
def func(a:Int,b:Int)= a+b
//柯里化定义
def func(a:Int)(b:Int)= a+b
//第一次
def first(a:Int)= a + (_ : Int)
val second = first(1)
//第二次
second(2) // 3

//从func中引用“第二步”函数
val second = func(1)_
  1. 柯里化的作用:隐式转化,更方便的组装函数
  2. 参数为单个参数时,scala允许用{}代替(),目的是为了方便程序员在{}中编写函数字面量。P175
  3. 传名参数用于替代函数参数无参数的表示。func:()=>Int 表示成 func:=>Int。传名参数完全可用传统参数替代,如funcValue:Int。但这种表达在调用函数前会被先求值,只有在明确不希望调用前参数先求值的场景下,传名参数才有用。P177

补充:
由于函数和字段在同一个命名空间,所以可以把无参函数和字段相互替换使用。即val a=”3”,可以把a当作一个无参函数使用。说明这点的作用是在做scala测试的时候,ScalaTest框架,会把测试代码当作无参函数传入test函数中。这样也利用了传名函数不会马上计算结果的特征。P260

//验证:
scala>   def strToInt(s: => String) = {s.toInt}
strToInt: (s: => String)Int
scala> val a ="3"
a: String = 3

scala> def f:String="3"
f: String

scala> strToInt(a)
res9: Int = 3

scala> strToInt(f)
res10: Int = 3

scala> strToInt("3") //可以看到,字面量”3“也可当作无参函数使用。
res11: Int = 3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值