[Scala基础系列 05]Scala控制结构

1.If语句

Scala的If语句可以完成其他语言中If语句,于此同时,if/else通常还有值,可以用来赋值,或者代替三元条件运算符(?:)。不过它可以比条件运算符更强大,因为你可以在if-else里面写很复杂的程序块。

1.1.普通的If语句

package com.tv189.foundation

/**
 * Created by molyeo on 2015/7/30.
 */
object IfCondition {
  def main(args: Array[String]) {
    val checkPoint = 80
    def check(current: Int): Unit = {
      if (current >= checkPoint) {
        println("Success")
      } else {
        println("Fail")
      }
    }
    check(80)
    check(90)
    check(30)
  }
}

1.2.用If语句赋值

if/else语句能用来赋值,进而代替?:运算符,得益于Scala的一个重要特点,在Scala中,每个语句块都有值,就是该语句块最后一个语句的值。请看下面的代码。

def printAbs(x: Int) {
  val abs = if(x < 0) -x else x
  println("The Abs of %d is %d".format(x, abs))
}

def abs(x: Int) = if(x < 0) -x else x

printAbs(-5)
println(abs(-6))

第一个函数printAbs里,用了一个if-else语句给abs赋值,这个语句的作用就相当于C语言或者Java中的?:操作符。 进一步的,由于语句块的最后一个语句的值,是整个块的值,语句块只有一个语句时,大括号{}可以省略。因此,求绝对值的函数可以简略至只有一行。

2.While语句

Scala的While语句与其他语言中While语句类似,有经验的程序员可以跳过这一小节。与If语句不同,While语句本身没有值。或者说,整个While语句的结果是Unit类型的()。

2.1.while循环

与大部分语言一样,Scala中的while循环包括循环体和循环条件两部分,循环条件满足时,循环体会一直执行下去。

package com.tv189.foundation

/**
 * Created by molyeo on 2015/7/30.
 */
object WhileCondition extends  App{

    def reverse(number: Int) = {
      var result = 0;
      var temp = number;
      while (temp != 0) {
        result = 10 * result + temp % 10;
        temp /= 10;
      }
      result;
    }

    println("The result of reverse(12345) is "+reverse(12345))

    def sum(input: Int) = {
      var total = 10;
      var temp = input;
      while (temp > 0) {
        total = temp + total;
        temp = temp - 1;
      }
      total;
    }
    println("The result of sum(10) is "+sum(10));

}

运行结果

The result of reverse(12345) is 54321
The result of sum(10) is 65

2.2.do-while循环

do-while循环与while循环的区别只在于,do-while循环是先执行循环体,然后再判断条件,这意味着循环体至少会被执行一次。

val i = 0
do {
  println("executed once")
} while( i != 0)

请注意
Scala中,赋值语句没有值,不像C和Java那样,因此在别的语言常见的while((c = getchar()) != ‘\n‘),Scala里面不能这样写。
这是因为,在Scala中,c = getchar(),虽然c会被赋值,但是该值不会赋给整个语句。这与其他语言不同,在C语言和Java中,c的值也会赋值给整个语句。

3.For语句

Scala的For语句跟其他语言差别很大,即使是有经验的程序员也需要仔细了解这部分内容。

在Scala中,没有形如for (initialize; test; update)的for循环,要实现相关功能,要么用while代替,要么,用Scala式的for语句for (i <- 1 to n)

3.1.用于集合的迭代

3.1.1.基本语法

用于迭代一个集合的for语句,格式为for(item <- collection),这有点像Java里的for (type var : arr),或者C#里的foreach(type var in arr)

由于Scala里创建一个集合的方式很多,所以for语句相对来说比较灵活,请看下面的示例。

package com.tv189.foundation

/**
 * Created by molyeo on 2015/7/30.
 */
object ForCondition extends App{
  val list=List("one","two","three","four");

  println("-------------use to-------------")
  for(i<-0 to list.length-1){
    print("  "+list(i));
  }

  println();
  println("-------------use until-------------")
  for(i<-0 until list.length){
  print("  "+list(i));
  }

  println();
  println("-------------use until-------------")
  for(item<-list){
    print("  "+item)
  }
}

请注意
请注意以上代码to和until的差别,to包含上边界,until则不包含。

3.1.2.嵌套for循环

Scala中,嵌套for循环比别的语言要容易,只要将表达式都放进for的括号里,用分号(;)隔开。比如:

for(i <- 0 to 1; j <- 1 to 2; k <- 2 to 3) {
    println("i = %d, j = %d, k = %d".format(i, j, k))
  }

以上代码,是i,j,k三个循环的嵌套。

3.1.3.条件for循环

for循环的循环器还可以带有条件语句,从而筛选循环的项目。比如:

for{i <- 0 to 5
    if i % 2 == 0
    j <- 1 to 2} {
  println("i = %d, j = %d".format(i, j))
}

以上代码中,i带有条件 i % 2 == 0,这意味着,仅有满足这个条件的i才会被循环执行。

3.2.中途绑定变量

请看下面的示例。

val list = List("Html", "XML", "JSON", "text", "md")
for{ ext <- list
    lower = ext.toLowerCase
    if lower != "html"} {
  println("Accepted data format: " + lower)
}

你可能已经发现了,上面代码中,toLowerCase在一个循环中可能会执行两次。这种情况我们希望能避免掉。 Scala的确也提供了这样的途径,你可以在for语句中途引入变量,然后在循环体内可以使用它。

上面代码中,我们引入了变量lower,这与通常我们定义变量的方式相比,只是少了val。这个变量在for表达式中和循环体中都可以使用。

3.3.用于产生新的集合

for语句,配合yield,可以用来产生新的集合。如下例所示。

val result =
  for(i <- 1 to 3; j <- 2 to 4)
  yield {
    i + j
  }
println(result)

上面代码产生了一个集合Vector(3, 4, 5, 4, 5, 6, 5, 6, 7)。

请注意
for-yield的格式是 for(claus) yield {body},不能将yield放到大括号内。

4.基本Match语句

Scala的Match语句功能基本上类似于其他语言中的switch-case,不过要强大很多。这一小节我们只介绍基本语法,关于“强大”的部分,留待模式匹配介绍。

用过switch-case语句的程序员知道,它的作用是让你在一系列选项中选择一个分支执行。mach与之类似,不过还是稍有差别。

"your selector" match {
  case "case 1" => //handle case 1
  case "case 2" => //handle case 2
  ...
  case _ => //handle the rest, like default in switch-case
}

除了上述语法上的差别外,match与switch语句,有如下的不同点。

  1. 任意类型的常量或结果为常量的表达式都可以用于match语句。许多语言的switch只允许基本类型,Java则更严格,只允许整形和枚举类型。
  2. 每个分支不需要以break结束,因为Scala里没有贯穿执行(fall through)。
  3. 与if表达式一样,match表达式也有结果。这也是可以理解的,match其实是多个分支的if的另一种写法。

我们看一个实际的例子。

def score(grade: Char) = {
  grade match {
    case ‘A‘ => println("85~100")
    case ‘B‘ => println("70~84")
    case ‘C‘ => println("60~69")
    case ‘D‘ => println("45~60")
    case ‘E‘ => println("<45")
    case _ => println("Input error")
  }
}

def passed_?(grade: Char) = {
  grade match {
    case ‘A‘ | ‘B‘ | ‘C‘ => true
    case _ => false
  }
}

score(‘B‘)

if(passed_?(‘B‘)) println("Congrats!")
else println("Uh~ oh, we are sorry...")

函数score包含一个常规的match语句,这里我们能看到,每个分支只执行本分支的代码,没有贯穿。最后有一个case _处理未列出的情况。Scala中下划线(_)经常用作通配符。

函数passed_?有两处需要注意。一个是这个函数的返回类型是Boolean,由于该函数体只有一个match语句,显然其返回值就是match语句的值。match语句的值,其类型就是各个分支的共同类型。 如果这些类型不相同,就取最近的共同父类。

另一个值得注意的地方是,case ‘A‘ | ‘B‘ | ‘C‘ => true这正是其他语言中,switch-case贯穿执行的情况,类似于

case ‘A‘ :
case ‘B‘ :
case ‘C‘ : return true;

这也从一个侧面说明了,Scala里面,取消break和fall-through,能让代码更简洁,语义更清晰,而且让程序员不容易犯无心之过。

5.异常处理

Scala的异常处理与其他语言是一样的,功能一样,语法也只有细微的差别。异常机制的出现是为了处理程序中不正常的情况,比如要查找的文件不存在,数据库连接不上,网络中断等等。 当碰到异常情况时,方法抛出一个异常,终止方法本身的执行,异常传递到其调用者,调用者可以处理该异常,也可以升级到它的调用者。运行系统会一直这样升级异常,直到有调用者能处理它。 如果一直没有处理,则终止整个程序。

5.1.抛出异常

Scala中异常的抛出与Java一样,用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型,这与Java里一样。事实上,Scala的Throwable就是java.lang.Throwable的别名。

正如在底层类型Nothing里介绍过的那样,throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。

def divide(x: Int, y: Int): Int = {
  if (y == 0) throw new Exception("Divide by zero")
  else x / y
}

以上代码divide方法的返回类型是Int,由于throw new Exception("Divide by zero")的类型是Nothing,是Int的子类,所以上述代码是正确的。

5.2.捕捉异常

异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。

捕捉异常的catch子句,语法与其他语言中不太一样。在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句,如下例所示。

import java.io._
import java.lang.SecurityException

try {
  val file = new File("myfile")
  val stream = new FileInputStream(file)
  // use the stream
}
catch {
  case ex: FileNotFoundException => println("File not found")
  case ex: SecurityException => println("You don‘t have read access to the file")
  case _: Throwable => println("Other exception happened")
}

catch字句里的内容跟match里的case是完全一样的。由于异常捕捉是按次序,如果最普遍的异常,Throwable,写在最前面,则在它后面的case都捕捉不到,因此需要将它写在最后面。

5.3.finally字句

finally字句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作。如下面伪代码所示,Scala的finally跟其他语言是一样的。

val file = getFile
try {
  //use the file
}
catch {
  //handle exception
}
finally {
  //close the file no matter how above code goes
}

中断循环

Scala中没有break或continue关键字,这代表Scala不鼓励使用break或continue跳出循环。不过,如果实在想用的话,Scala提供了相应的类和方法。

使用break

如果确有需要,可以使用Breaks类的break方法,来实现退出循环的功能。如下例所示。

import scala.util.control.Breaks._

val list = List("functions.md", "menu.json", "index.html", "data.xml")

var found = false
breakable {
  for(item <- list) {
    if(item.endsWith("html")) {
      found = true
      break
    }
  }
}

if(found)
  println("There is at least one html file in the list")
else
  println("There is no html file in the list")

类似这样的代码,我们在指令式编程中,比如Java,C语言等,经常会看到。但是实际实现上还是有差别的。Scala的break是Breaks类的一个方法,所以,上述代码首先引入了Breaks类。

import scala.util.control.Breaks._

另外,break实际上是靠的抛出一个异常来实现的,源代码是

def break(): Nothing = { throw breakException }

这里,breakException是一个ControlThrowable类型的异常。因此,你的代码还需要用breakable包围起来。breakable的作用是捕获break抛出的异常,以免影响你真正的产品代码。其源代码类似于

def breakable(op: => Unit) {
    try {
      op
    } catch {
      case ex: BreakControl =>
        if (ex ne breakException) throw ex
    }
  }

在Scala里使用break真的不太方便,continue呢,则压根儿就没有,所以,咱最好还是别用了。这正是Scala的设计者所期待的,避免使用break和continue。

避免使用break和continue

避免使用break和continue完全是可以做到的。比较简单和符合我们编程固有习惯的方式,就是用while循环,将终止条件放到while字句里,比如上面的例子,要修改的话,可以将found放进while字句。

var found = false
while(!found) {
  for(item <- list) {
    if(item.endsWith("html")) {
      found = true
    }
  }
}

以上是一种可行的办法,但是,还不是Scala设计者希望看到的。他们希望看到的是程序员们能具有函数式编程的思想。还是拿上面的例子来说,从函数的角度考虑, 以上所需要完成的功能,用一个函数来表达,就是在一个字符串列表中寻找以html结尾的字符串,这个函数可以定义如下面的hasHtml。

val list = List("functions.md", "menu.json", "index.html", "data.xml")

def hasHtml(input: List[String]): Boolean = {
  input match {
    case Nil => false
    case x :: sub => {
      if(x.endsWith("html")) return true
      else hasHtml(sub)
    }
  }
}

if(hasHtml(list)) println("There is at least one html file in the list")
else println("There is no html file in the list")

hasHtml是比较容易理解的,如果list里面没有元素,当然是false,然后将list看成两部分,第一个元素,和剩余的子列表。 如果第一个元素是html的,那么就已经找到了,如果不是,则继续在子列表里面查找。这样的思考问题方式,比较容易理解和交流。 在子列表查找,和原始问题是一样的,只是输入集在缩小,这是一个典型的递归问题。事实上,函数式编程几乎离不开递归。 更特别的,以上hasHtml是一个尾递归,大部分函数式语言编译器都对尾递归有优化,Scala也不例外。对程序员来说,不用担心使用尾递归会带来性能下降。 关于尾递归,后续文章会进行讨论。

从指令式的思维转变到函数式,需要时间,不要强求瞬间完成。不过从现在开始,我们可以慢慢体会函数式编程的优雅。

参考文献:

http://meetfp.com/zh/scala-basic/break

时间: 2024-10-09 19:59:09

[Scala基础系列 05]Scala控制结构的相关文章

[Scala基础系列 04]Scala基本类型

1.Scala的数值类型 Scala的数值类型与Java类似,他们的范围与Java也是一致的.与Java不同的是,他们都是对象,是相应的数值类的实例.Scala通过富包装(Rich Wrapper)类给这些数值类型提供了强大的支持. 1.1.数值类型 Scala的数值类型和取值范围,见下表. Boolean: true 或者 false Byte: 8位, 有符号(2-7 ~ 27 - 1) Short: 16位, 有符号 (2-15 ~ 215 - 1) Int: 32位, 有符号 (2-31

[Scala基础系列 08]Scala继承、抽象类、接口trait以及AOP实现

1.继承 和java一样,scala采用extends关键字继承基类.代码实例如下: /** * Created by molyeo on 2015/8/11. */ class Person(val name: String, var age: Int) { println("The primary constructor of Person") val school = "BJU" def sleep = "8 hours" override

[Scala基础系列 10]Scala泛型、类型边界

1.泛型和类型边界 1.1.上限(upper bounds) 我们先看一个简单的实例,用于判断两个变量中较大的值,其中两个变量的类型均为Int型 package com.tv189.advanced /** * Created by molyeo on 2015/8/12. */ class PairInt(val first: Int, val second: Int) { def bigger = { if (first.compareTo(second) > 0) first else s

[Scala基础系列 03]Scala操作符

1.Scala操作符简介 首先,请记住,Scala没有操作符!也没有通常意义上的表达式.你所见到的类似操作符和表达式的语句,其实是方法(函数),它们只是方法的一种比较直观的写法,可以叫做操作符记法. 1.1.二元操作符(中缀表达式) 二元操作符是最常见的操作符,比如,一个简单的表达式1 + 2.其实,“+”是定义在Int类的一个方法,你完全可以用普通方法调用的写法1.+(2).相应的,其他的方法,比如"Hello".drop(2),也可以用操作符记法,"Hello"

[Scala基础系列 02]Scala函数

本文主要内容如下: 变量和不变量 函数和过程 函数的参数 分号 1.变量和不变量 1.1.变量 Scala的变量分两种,var和val.var,即variable,类似于我们在Java等其他语言中接触到的变量,而val,是value,类似于我们在其他语言中用到的不可重新赋值的常量,或者final变量. 为什么会有这种区别,这是由于很多情况下,其实你不需要一个可变的var,尤其在函数式编程中,更为明显.不变性能给程序带来很多便利,因此Scala非常强调不可变(immutable)的概念.关于不可变

[Scala基础系列 06]Scala类和对象

1.类和构造函数 Scala中的类,基本概念与其他面向对象语言是一致的,不过在语法上有些不一样的地方.与Java等语言相比,Scala的类语法更简洁,使用起来也更方便. 1.1.类的基本语法 我们先来看一个简单的类定义和使用的代码. class ScoreCalculator { private var total, count = 0 def report(score: Int) { total += score count += 1 } def score = if (count == 0)

[Scala基础系列 07]Scala集合

Scala有一个非常通用,丰富,强大,可组合的集合库:集合是高阶的(high level)并暴露了一大套操作方法.很多集合的处理和转换可以被表达的简洁又可读,但不审慎地用它们的功能也会导致相反的结果.每个Scala程序员应该阅读 集合设计文档:通过它可以很好地洞察集合库,并了解设计动机. 1.数组(Array&ArrayBuffer) 1.1.Array 数组(Array)其实并不在scala.collection包里面,它属于scala包,直接对应于Java的数组,比如,Scala中的Arra

第1节 Scala基础语法:scala中的方法源码分析

val list=List(1,2,3,4) list.reduce((x:Int,y:Int)=>x+y)--->list.reduceLeft((x:Int,y:Int)=>x+y) var first = true var acc:Int = 0 op=(x:Int,y:Int)=>x+y for循环第一次循环:acc=1 first = false第二次循环:acc=op(1,2)=1+2=3第三次循环:acc=op(3,3)=3+3=6第四次循环:acc=op(6,4)=

Power BI基础系列-05.可视化设计

可视化设计基本要求 在Power BI中,报表可以有一个或多个报表效果,所以页面统称为报表,报表的基本元素包含视觉对象(可视化效果).独立图像和文本框等等.从各个数据点到报表元素,再到报表页面本身,有多种格式选项可供选择. 报表生成工作在生成第一个视觉对象之前就开始了,因为优质的报表需要提前规划.了解需要处理那些数据.并记录要满足的报表需求:业务需求是什么?此类数据的使用方式是什么?用户数谁?用户希望能够根据此报表做出那些决策? 这些问题的答案决定了设计的方向,确保每张报表所传达的信息能够满足业