你好,游客 登录
背景:
阅读新闻

第4章 Scala基本控制结构

[日期:2021-09-26] 来源:  作者: [字体: ]

本文来自艾叔编著的《零基础快速入门Scala》免费电子书,添加文末艾叔微信,获取完整版的PDF电子书

第4章  Scala基本控制结构

本章介绍Scala基本控制结构,正常情况下,Scala代码是一条条向下,顺序执行的,但很多情况下,需要根据条件,来决定程序的运行路径,这就需要控制结构。同基本数据类型一样,控制结构也是编程语言的重要组成部分。Scala的基本控制结构包括:if语句、case语句、for循环、while循环等,它们可以控制程序走不同的路径,或者完成不同的动作,具体如下。

4.1  if语句

if语句的使用例子如下:if判断条件表达式printHello的值,如果为true,则进入第3行所在的分支执行,否则,进入第5行所在的分支执行。

      1 var printHello=true

      2 if(printHello){

      3   println("Hello!")

      4 }else{

      5   println("World!")

      6 }

注意下面的用法,这是C/C++中的常用做法,对变量printHello赋值,然后判断printHello的值,来决定执行哪个分支。

      1 var printHello=true

      2 if(printHello=false){

      3   println("Hello!")

      4 }else{

      5   println("World!")

      6 }

但是,上述代码,在Scala中是有问题的,如果执行,会报下面的错误:

<console>:13: error: type mismatch;

 found   : Unit

 required: Boolean

这是因为,printHello=false并不会像C/C++那样,返回printHello的值,然后由if再来判断printHello的值是否为真。Scala中,printHello=false是一个表达式,这个表达式的类型是Unit,它不是Boolean型的,因此报错。

ifwhile中的条件表达式一定要注意其返回值。

4.2  match-case(模式匹配)

1. Match-case基本使用,值匹配

if语句相比,match-case可以将各种判断列出来,程序代码在逻辑上更清晰,而且扩展更方便,不容易出错。casematch搭配一起用,示例代码如下.

      1 inpuType match {

      2   case 0 => println("input type is 0")

      3   case 1 => println("input type is 1")

      4   case 2 => println("input type is 2")

      5   case _ => println("input type is other")

      6 }

inputType是一个Int类型的变量,通过match-case来判断inputType的值,然后做出不同的动作,如果是0,则打印“input type is 0”,如果是1,则打印“input type is 1”,下划线_表示其它所有的情况。

case匹配后会直接跳出,例如,inputType的值为0时,Scala语言匹配到0,直接打印“input type is 0”,然后退出match-case。如果是C/C++/Java(使用switch-case),如果不在println语句后面加上break的话,则还会依次去匹配12等,这样就可能造成逻辑混乱。

如果要在匹配后,执行多条语句,只需在这些语句前后加上大括号即可,示例代码如下所示。

      1 case 0 => {

      2   println("input type is 0.1")

      3   println("input type is 0.2")

      4 }

2. class匹配与值抽取

match-case可以判断引用的类型,同时 还能从被match对象中,抽取值到变量中。示例代码如下。

      1 var inputType:Any = null

      2 //inputType = "Hello World!"

      3 inputType = 10

      4 inputType match {

      5   case str:String => {

      6     println("inputType is String")

      7     println(str)

      8   }

      9

     10   case num:Int => {

     11     println("inputType is Int")

     12     println(num)

     13   }

     14

     15   case _ => println("other type")

     16 }

上面的代码对inputType引用进行类型判断,inputType被声明为Any类型,Any是所有class的父类,因此,所有子类的值都可以赋给它,第2行是将String类型的引用赋值给inputType,而第3行则是将Int类型的引用赋值给inputType,因为StringInt都是Any的子类,因此,这样的赋值是没有问题的。第4~16行,判断inputType的类型,根据不同的类型做不同的动作,其中第5~8行匹配String类型,如果inputTypeString类型,就会将inputType赋值给str,然后执行第5行起到第8行结束的大括号中的语句,在这段语句中,可以引用str,例如打印str等;第10~13行,则是匹配Int类型,一旦匹配,则将inputType赋值给num,然后执行第10行到第13中大括号内部的语句,同样可以使用num

下面再来看一个更深入的例子,来体会一下Scalamatch-case的强大。下面的代码对person的类型进行判断,分别判断person的类型是普通的personteacher还是student,然后根据不同的类型,抽取不同的值到变量中,进行相应处理,示例代码说明如下。

1行声明personAny类型,这是为后续给person赋其它子类的值做准备;

2~4行,是分别给person赋不同类型的值,第2行是普通person,它只有1个值,就是名字;第3行是teacher,它有2个值,名字和工作;第4行是student,它有3个值,名字、工作和id

Scala可以将多个类型的值组合在一起,构成一个新的对象,在scala中称之为元组(Tuple),例如(rose,student,123456),就是将StringStringInt三种类型的值组合在一起,构成一个新的对象,元组中的每个元素,可以使用下划线加数字来引用,person中第一个元素,就是person._1,第二个元素就是person._2,第三个元素就是person._3

5~10行,对person进行匹配,如果person中有2个元素,则执行第7行的=>后面的语句,在语句中,可以使用teacher._1来引用元组中的第一个元素,使用teacher._2来引用元组中的第二个元素;同样的,如果person中有3个元素,则执行第8=>后面的语句。

      1 var person:Any = null

      2 //person = ("mike")

      3 person = ("tom", "teacher")

      4 //person = ("rose", "student", "123456")

      5 person match {

      6   case per:(String) => println("name " + per)

      7   case teacher:(String, String) => println("name " + teacher._1 + " ocupation " + teacher._2)

      8   case stu:(String, String, Int) => println("name " + stu._1 + " ocupation " + stu._2 + " id " + stu._3)

      9   case _ => println("other type")

     10 }

3. match的返回值

match-case语句是有返回值的,其返回值就是=>右边语句段中,最后一句的返回值。例子如下:

      1 val input: Int = 1

      2 val rs = input match {

      3   case 0 => "num 0"

      4   case 1 => "num 1"

      5   case _ => "other value"

      6 }

      7 println("rs " + rs)

执行代码的话,会打印:

rs num 1

1. 如果=>后面返回的值有多种类型,例如,case 0后面的返回值是Int,而case 1后面的返回值是String,那么match返回值是Any类型;

2. match其实是一个方法,在IDEA中,输入input变量,后面跟一个点(.),就会出现match

3. 利用match的返回值,不需要在case语句中加入外部变量,就可以方便地将值带回来。

4.3  for

1. for基本使用

for的基本使用例子代码如下。

      1 for(i <- 0 until 5){

      2   print(i+" ")

      3 }

上述代码执行后的输出如下。

0 1 2 3 4

上面的代码,首先执行0 until 5(其实是0.until(5)),生成0~4的集合(Range),然后从0开始,依次遍历,执行for后面大括号{}中的内容,在{}中,可以访问i,并做相应的动作。

until生成的是减一的Range,如果要不减,则可以使用to

      1 for(i <- 0 to 5){

      2   print(i+" ")

      3 }

上述代码执行后的输出如下。

0 1 2 3 4 5

2. for返回值

我们可以使用yield收集for循环中产生的值,并返回,例子代码如下。

1行生成0~4Range,然后遍历每个元素,乘以2,放入类型为IndexedSeq的集合,所有元素遍历完后,赋值给rs

2行遍历rs,每个元素打印一行输出。

      1 val rs = for(i <- 0 until 5) yield i*2

      2 rs.foreach(println)

yield必须跟在for的小括号后面;

Yield返回的类型和for中遍历对象的类型是一致的。

3. for当中可以加入条件判断(守卫)

for遍历时,可以加入条件判断,Scala称之为守卫。

下面的例子代码遍历数组ar,并找出所有是偶数的元素,返回给rs。第2行中,if((i%2)==0)判断i是否为偶数,这就是一个守卫,只有符合条件的(偶数),才会进入for后面的执行部分,即yield i

      1 val ar = Array(0, 1, 2, 2, 5, 4, 8, 9)

      2 val rs = for(i <- ar if ((i%2)==0)) yield i

      3 rs.foreach(x=>print(x+" "))

上述示例代码执行结果输出如下。

0 2 2 4 8

如果要加入多个判断,例子代码如下,第4行为第一个判断条件,第5行为第二个判断条件,yield返回大于2的所有偶数,因为arArray[Int]rs的类型也是Array[Int]

      1 val ar = Array(0, 1, 2, 2, 5, 4, 8, 9)

      2 val rs = for{

      3   i <- ar

      4   if ((i%2)==0)

      5   if(i>2)

      6 } yield i

      7 rs.foreach(x=>print(x+" "))

4. 双重for循环

有的时候需要双重(多重)循环,例如,打印9*9乘法表,代码如下。

      1 for(i <- 1 to 9; j <- 1 to 9 if(i>=j) ){

      2   print(j + "*" + i + "=" + i*j + " ")

      3   if(i==j) println

      4 }

1行使用了ij的双重循环,中间使用分号隔开,并且使用了if(i>=j)的守卫,过滤所有i<j的元素,避免重复;

2~3行输出打印的数据和结果。

最终输出结果如下:

1*1=1

1*2=2 2*2=4

1*3=3 2*3=6 3*3=9

1*4=4 2*4=8 3*4=12 4*4=16

1*5=5 2*5=10 3*5=15 4*5=20 5*5=25

1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36

1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49

1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64

1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

4.4  while

1. while基本使用

while基本使用的例子代码如下,功能是打印数字0~4

2行是while的控制部分,小括号内是一个条件表达式,如果表达式的值是真,则执行第3~4行,并返回到第2行,再次判断条件表达式的值,如果表达式的值为假,则直接从第2行跳转到第6行;

2行开始的大括号{,到第5}结束,大括号内部是while的执行代码,如果条件表达式的值为真,则会依次执行大括号{}内部的代码。

      1 var i = 0

      2 while(i<5){

      3   print(i + " ")

      4   i+=1

      5 }

      6 println

要特别注意while后面条件表达式:1. 表达式的类型必须是boolean型;2. 仔细考虑表达式为false的情况,即退出while循环的情况,防止死循环。

2. whilefor的区别

1优化代码时,while循环和for循环之间的效率可能有10倍的差距

2一般情况下,单层循环,while效率 > for

3双层循环下,while效率可能 < for

4.5  breakcontine

1. break使用

Scala程序有时需要在循环过程中,直接跳出循环,在C/C++/Java中,使用break来实现,Scala也有break,但使用相对麻烦一点。

break例子代码如下,其功能是:当i=2时,使用break跳出while循环,关键代码说明如下。

2行,引入break所需的package

4行,breakable是一个函数块,将循环(whilefor)放入此函数块中;

7���,当i等于2时,使用break函数,跳出while循环。

      1 var i = 0

      2 import scala.util.control.Breaks._

      3

      4 breakable {

      5   while (i < 5) {

      6     i += 1

      7     if (i==2) break()

      8   }

      9 }

     10 println(i)

import可以出现在代码的任何位置,只要在使用此package的语句前就可以;

break不可直接使用,要联合breakable语句块;

breakable的使用会使得循环效率降低,在一些性能关键的循环处,要谨慎使用。

2. continue使用

Scala程序在循环过程中,有时需要停止向下执行代码,回到循环开始处,开始下一轮新的循环,C/C++/Java提供了continue来实现此功能。Scala中没有专门的continue关键字,但可以用break来实现类似的功能,

Scala例子代码如下。下面的代码遍历0~4,当i=2时,跳出,重新回到第3while处,实现了类似continue的功能。

      1 var i = 0

      2 import scala.util.control.Breaks._

      3 while (i < 5) {

      4   i += 1

      5   breakable {

      6     if (i == 2) break()

      7     println("in loop " + i)

      8   }

      9 }

要注意的是:

breakable函数块提到了while内部;

break()只是跳出breakable函数块,如果breakable函数块外部还有代码,也就是第8行开始,往下还有代码的话,会继续执行,这是和continue不一样的地方,continue会直接跳转到while,因此,如果要实现类似continue的功能,就要确保breakable函数块后面没有代码。

 

加艾叔微信,加入Linux(Shell+Zabbix)、大数据(Spark+Hadoop)、云原生(Docker+Kubernetes)技术交流群

关注艾叔公众号,获取更多一手信息

收藏 推荐 打印 | 阅读:
相关新闻