Scala学习笔记DAY2--控制结构和函数

在Scala中,换行可以代替分号的作用

条件表达式

Scala的if/else表达式有值,这个值就是跟在if或者else之后的表达式的值。

在Scala中每个表达式都有一个类型。比如if (x>0)1 else -1的类型是Int;混合类型表达式if(x>0) “positive” else -1的类型是两个分支类型的公共超类型。也就是Any类。如果else语句缺失了,如if(x>0)1 这个语句中可以输出值,但是因为每个表达式都应该有某种值,因此引入Unit类(类似于void,但是void没有值,Unit有一个表示“无值”的值),写作()表示“无有用值”的占位符。因此上述表达式也可以写成if(x>0)1 else ()
REPL的近视问题:对于如下的代码,解释器在同一时间只能看见一行代码,因此只会在执行if(x1<0) -1之后就显示结果,而对下一行的内容不知所措

1
2
if (x1<0) -1
else if (x1==9) 0 else 1

解决方法:

  1. 使用花括号

    1
    2
    if (x1<0){ -1
    } else if (x1==9) 0 else 1
  2. 使用Scala的粘贴模式
    :paste
    //复制代码)
    按下ctrl+D

语句终止

在Scala中行尾不需要分号只要能从上下文明确的判断这里是终止位置即可;如果需要在单行写下多个语句,那么就需要使用分号隔开,否则报错

一般如果语句比较长,需要分两行来写,但是又担心解释器的近视问题,那么可以使用一个不能作为语句结束的符号作为结尾,比如+号,或者使用{号,在使用左括号之后编译器只有在看见匹配的右括号才回去推断某事是否是语句的结尾。

块表达式和赋值

在Scala中,块{}中包含了一系列的表达式,其结果也是一个表达式。块中最后一个表达式的值就是这个块的值。这个对于某些val初始化需要很多步的情况有用,可以使用一个语句结束

在Scala中,赋值动作的值的类型是Unit类型,也就是说赋值动作是没有值的值,因此下面的语句不会把10赋予x3:

这样的x3的类型就是Unit

输入和输出

使用readLine读取控制台的一行输入;使用readInt,readByte,readShort,readBoolean,readChar等读取特定的类型。readLine带一个参数作为提示符。

Scala可以使用java风格的也可以使用C++风格的输出

循环 while do

1
2
3
4
while(n>0){
r=r*n
n-=1;
}

scala有for循环,但是没有for(初始化变量;检查变量是否满足条件;更新变量)这样的对应结构。因此只能使用如下两种循环
1.for(i<-表达式)//让i遍历<- 右边的表达式的所有值,如果这个表达式的类型是Range,那么就会让i一次取得区间中的每个值

1
2
for(i<-1 to n)
r=r*i


//注意在变量i之前并没有类型的指定,实际上这个变量的类型是集合的元素类型,这个循环变量的作用于是循环内部。
until方法 返回一个并不包含上限的区间

1
for(i<-0 until s.length)//遍历下标为0到s.length-1的字符串中的字符

对于字符串还可以直接遍历

1
for(ch<-"Hello") sum+=ch

在Scala中没有提供break或者continue退出循环,因此可以通过下面的方法来退出循环

  1. 使用Boolean型的控制变量
  2. 会用嵌套函数——从函数中return
  3. 使用breaks对象的break方法,但是控制权的转移是通过抛出和捕获异常完成的,因此时间很重要的话,应该尽量避免使用这套机制。
    1
    2
    3
    4
    5
    6
    7
    import scala.util.control.Breako._
    breakable{
    for(...){
    if(...) break;//退出breakable块
    ...
    }
    }

高级for循环和for推导式

可以使用变量<-表达式的形式提供多个生成器(大胆推测这里的生成器是一种类型,Range,Vector都是生成器的子类),用分号隔开。类似于java的嵌套for循环

可以给每个生成器带一个守卫,以if开头的Boolean表达式:

可以在for语句中使用任意多的定义,引入可以在循环中使用的变量(这里不能使用var from=4-i)

上面的形式和下面的语句是一个含义

for推导式:如果for循环的循环体以yield开始,该循环会构造出一个集合,每次迭代生成集合中的一个值,注意生成的集合与for循环中第一个生成器的类型是兼容的(学习思考:第一个生成器类型是char,结果是String;第二个生成器类型是Range,但是结果是IndexedSeq。不是很理解):

函数

java中往往使用静态方法来模拟函数。
定义函数:

使用函数和使用方法差不多

定义一个需要多个表达式的函数体,可以使用代码块{},块中最后一个表达式的值就是函数的返回值,scala中不经常使用return退出,但是可以使用return。

默认值参数和带名参数

在定义函数的时候可以给定默认值,当使用函数的时候,如果没有输入对应参数,那么就使用默认值代替。

在使用函数的时候,如果指定了参数名,那么参数的顺序不一定要按照定义的顺序

在调用函数的时候,可以混用未命名参数和带名参数。需要注意的是,无名参数值需要排在前面。

变长参数

scala中可以定义可变长度参数的函数

在这里函数的输入是一个类型是Seq的参数。

需要注意的是,不能传入1 to 5这种形式,这是一个区间的形式,而函数需要的是调用的时候传入单个参数。可以通过如下的方式告知编译器你希望这个参数被作为参数序列处理:
*这个可以将区域转换为参数序列

在递归定义中我们会用到上述语法(这里不是很明白,按照s上下文的意思,1,2,3,4,5这种形式已经算是Seq类型了,这个类型正是函数需要的,那么在函数内部,args.tail得到的也是Seq类型的,为什么还需要:
*这个呢?)

实际操作之后,发现函数内部需要的是Int型的参数,但是输入的是Seq,这是不是说明前面的内容说错了?函数需要的是Int型,而不是Seq,期待后面的13章的解释。

下面的这一段我没有看懂,试着打了但是控制显示不知道

过程(procedure)

函数体包含在花括号中,但是没有前面的“=”号。也就是没有返回值的函数,也就是说返回类型是Unit.这里可以有“:Unit =”但是前面的这种写法比较简单。

懒值

当val被声明为lazy之后,其初始化将被推迟到我们首次对它取值的时候。这个对于开销比较大的初始化语句有用,比如说访问文件操作等。还可以用于应对循环依赖的问题。这是开发懒数据结构的基础。
val,lazy val ,def的关联

异常

和java一样,如果抛出异常,那么当前的运算被终止,同时系统查找可以接受抛出异常的异常处理器,如果找到就进入处理器处理,如果没有找到则退出程序。抛出的异常必须是java.lang.throwable的子类,scala没有受检异常,因此不需要再定义函数和方法时说明函数或者方法可能抛出的异常。
之前提到每个Scala表达式都有返回值类型,而throw语句的类型是Nothing,因此如果在if/else中某个分支使用抛出异常,那么这个if/else表达式的类型就是另一个分支的类型。

捕获异常所采用模式匹配的语法,当不需要使用异常对象时,可以用”_”代替异常对象的变量名。finally语句可以用于释放资源,在异常还没有处理的时候执行,和java一样一样的。

1
try { ... } catch { ... } finally { ... }

练习题答案

  1. 一个空的块{}是Unit类型的,值是()
  2. 当x的类型是Unit时
  3. 使用Range的reverse方法
  4. 第五题有点奇怪在函数中使用4题的答案反而出不了结果,显示reverse的使用有问题。
  5. 有的时候不能完全依赖scala的自动类型匹配
  6. StringOps中有一个方法是foreach,可用于对所有的元素执行某种运算。
  7. 使用递归的方式