快学Scala 2

控制结构和函数

1.在Scala中,几乎所有构造出来的语法结构都有值。这个特性是为了使得程序更加精简,也更易读。

  (1)if表达式有值

  (2)块也有值——是它最后一个表达式的值

  (3)Scala的for循环就像是“增强版”的Java for循环

  (4)分号(在绝大多数情况下)不是必须的

  (5)void类型是Unit

  (6)避免在函数定义中使用return

  (7)注意别在函数式定义中漏掉了=

  (8)异常的工作方式和Java中基本一样,不同的是catch语句中使用“模式匹配”

  (9)Scala没有受检异常

 

2.条件表达式

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

    if(x > 0) 1 else -1

  可以将if/else表达式的值赋值给变量:

    val s = if(x > 0) 1 else -1

  这与如下语句的效果一样:

    if(x > 0) s = 1 else s = -1

  不过第一种写法给号,因为它可以用来初始化一个val;而第二种写法中,s必须是var

  Scala中每个表达式都有一个类型。例如,表达式 if(x > 0) 1 else -1的类型是Int,因为两个分支的类型都是Int。混合类型表达式,比如if(x > 0) "positive" else -1,上述表达式的类型是另个分支类型的公共超类型。java.lang.String和Int的公共超类型叫做Any。

  如果else部分缺失了,比如:if(x > 0) 1,那么有可能if语句没有输出值。但是在Scala中,每个表达式都应该有某种值。解决方案是引入一个Unit类,写作()。不带else的if语句等同于:if(x > 0) 1 else ()

  把()当做表示“无有用值”的占位符,将Unit当做Java中的void。

  Scala没有switch语句,不过它有一个强大得多的模式匹配机制。现阶段,用一系列的if语句就好。

 

3.REPL比起编译器来更加“近视”——它在同一时间只能看到一行代码。可用花括号:

  if(x > 0)  {1

  } else if(x == 0) 0 else -1

只有在REPL中才会有这个顾虑。在REPL中粘贴成块代码,可用使用粘贴模式。键入:       :paste  把代码块粘贴进去,然后按下Ctrl+D。这样REPL就会把代码块当做一个整体来分析。

 

4.语句终止

  在Scala中——与JavaScript和其他脚本语言类似——行尾的位置不需要分号。不过,如果你想要在单行中写下多个语句,就需要将它们以分号隔开。例如:

    if(n > 0) { r = r * n; n-=1}

  如果写较长的语句,需要分两行来写,就要确保第一行以一个不能用来做语句结尾的符号结尾。通常来说一个比较好的选择是操作符:

    s = s0 + (v- v0) * t + //+告诉解析器这里不是语句的末尾

      0.5 * (a - a0) * t * t

   如果倾向于使用分号,用就是了——他们没啥坏处。

 

5.块表达式和赋值

  在Java中,语句块是一个包含于{}中的语句序列。在Scala中,{}快包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。

  这个特性对于val的初始化需要很多步完成的情况很有用。例如:

    val distance = { val dx = x - x0; val dy = y - y0;sqrt(dx * dx, dy * dy) }

  在Scala中,赋值动作本身是没有值的——或者,更严格地说,它们的值是Unit类型的。

    一个以赋值语句结束的块,比如{r = r * n; n-=1 }的值是Unit类型的。当我们定义函数时需要意识到这一点。

 

6.输入和输出

  如果要打印一个值,用print或者println函数。另外,还有一个带有C风格格式化字符串的printf函数:

    printf("Hello",  %s! You are %d old. \n", "Fred", 42) //  Hello,  Fred! You are 42 old.

  用readLine函数从控制台读取一行输入。也可以用readInt、readDouble、readByte、readShort、readLong、readFloat、readBoolean、readChar。不过,readLine带一个参数作为提示字符串:

    val name = readLine(”Your name:”)

    print(“Your age: ”)

    val age = readInt()

    printf("Hello, %s! Next Year, you will be %d .\n, name, age + 1)

 

7.循环

  Scala拥有与Java和C++相同的while和do循环。例如:

    while(n > 0) {

      r = r * n;

      n -=1;

    }

  Scala没有与for(初始化变量;检查变量是否满足条件;更新变量)循环直接对应的结构。如果需要这样的循环,有两种选择:一是使用while循环,二是使用如下for语句:

    for(i  <-  表达式)  让变量i遍历 <- 右边的表达式的所有值。至于这个遍历具体如何执行,则取决于表达式的类型。对应Range而言,这个循环会让i一次取得区间中的每个值。

  例如:

    for(i <- 1 to n)

      r = r * i

  遍历数组和字符串时,需要0到n-1的区间,可以使用until方法而不是to方法。util方法返回一个并不包含上限的区间。

    val s = "Hello"

    var sum = 0

           for(i <- 0 util  s.length)  //[0,s.length-1]

Scala中并没有提供break或continue语句来退出循环。如果需要break语句:

  (1)使用Boolean型的控制变量

   (2)使用嵌套函数——从函数当中return

  (3)使用Breaks对象中的break方法:

    import scala.util.control.Breaks._

    breakable {

      for(…) {

      if(...) break;

      ...

      }

    }

这里,控制权的转移是通过抛出和捕获异常完成的,因此,如果时间很重要的话,尽量避免使用这套机制。

 

8. 高级for循环和for推导式

  (1)可以以 变量 <- 表达式 的形式提供多个生成器,用分号将它们隔开。

    for(i <- 1 to 3; j <- 1 to 3) print ((10 * i + j) + " ") //打印11 12 13 21 22 23 31 32 33

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

    for(i <- 1 to 3; j <- 1 to 3  if  i != j) print((10 * i + j) + " ") //打印12 13 21 23 31 32

  注意if之前并没有分号

  可以使用任意多的定义,引入可以在循环中使用的变量:

    for(i <- 1 to 3; from = 4 - i; j <- from to 3) print((10 * i + j) + " ") //打印 13 22 23 31 32 33

  如果for循环的循环体以yield开始,则该循环会构造一个集合,每次迭代生成集合中的一个值:

    for(1 <- 1 to 10) yield i % 3

    //生成 Vector(1, 2, 0, 1, 2, 0, 1, 2, 0, 1)

这类循环叫for推导式

for推到式生成的集合与它的第一个生成器是类型兼容的。

  for(c <- "Hello"; i <- 0 to 1) yield (c + i).toChar  //将生成"HIeflmlmop"

  for(i <- 0 to 1; c <- "Hello") yield (c + i).toChar //将生成Vector('H', 'e', 'l', 'l', 'o', 'I', 'f', 'm', 'm', 'p')

  也可以将生成器、守卫和定义包含在花括号中,并可以一换行的方式而不是分号来分隔它们:

    for{ i <- 1 to 3

      from = 4 - i

      j <- from to 3 }

 

9. 函数

  Scala除了方法还支持函数。方法针对对象进行操作,函数不是。不过在Java中我们只能使用静态方法来模拟。

  定义函数,给出函数的名称,参数和函数体,例如:

    def  abs(x : Double) = if (x >= 0) x else -x

    必须给出所有参数的类型只要函数不是递归的,就不用指定返回值类型。Scala编译器可以通过=符号右侧的表达式推断出返回值类型。

  如果函数体需要多个表达式完成,可以用代码块。块中最后一个表达式的值就是函数的返回值。例如:

    def  fac(n : Int) = {

      var r = 1

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

      r     //返回r的值

    }

  对于递归函数,必须制定返回值类型。例如:

    def fac(n : Int) : Int = if(n<=0) 1 else n * fac(n - 1)

  如果没有返回类型,Scala编译器无法校验n * fac(n - 1)的类型是Int。

 

10.默认参数和带名参数

  在调用某些函数时并不显式地给出所有的参数值,对于这些函数可以使用默认参数。例如,

    def  decorate(str : String,  left: String = "[",  right : String = "]") =

      left + str + right

  这个函数的两个参数left和right带有默认值 "[" 和 "]"。调用 decorate("hello")得到"[hello]"。

  如果想对参数的数量,你给出的值不够,默认参数会从后往前逐个应用进来。例如,decorate("hello",">>[")会使用right参数的默认值。

  可以在提供参数值的时候指定参数名,例如:decorate(left=“《《”,str = “hello”,right=“》》”),结果是"《《hello》》"。注意,带名参数并不需要跟参数列表的顺序完全一致。带名参数使得函数更加可读。

  可以混用未命名参数和带名参数,只要那些未命名的参数是排在前面的即可。

    decorate("hello", right = "}>>" //将调用decorate("hello", "[", "}>>")

 

11.变长参数

  实现一个可以接受可变长度参数列表的函数会更方便。

    def  sum(args: Int*) = {

      var  result = 0

      for(arg <- args)  result += arg

      result

    }

 可以使用任意多的参数来调用该函数: val s = sum(1, 4, 9 ,16, 25)

 如果已有一个值的序列,则不能将它传入上述函数。例如:val s = sum(1 to 5)  //错误

 如果sum函数被调用传入的是单个参数,那么该参数必须是单个整数,而不是一个整数区间。解决方案:告诉编译器你希望这个参数被当做参数序列处理。追加    :_*

    val s = sum(1 to 5 : _*)  //将1 to 5 当做参数序列处理

 

posted @ 2015-10-12 21:23  jht_newbie  阅读(329)  评论(0编辑  收藏  举报