就是个控制结构,Scala能有什么新花样呢?

2021-03-04 14:38:07 浏览数 (1)

导读

编程语言中最为基础的一个概念是控制结构,几乎任何代码都无时无刻不涉及到,其实也就无外乎3种:顺序、分支和循环。本文就来介绍Scala中控制结构,主要是分支和循环。

Scala中的控制结构实质上与其他编程语言并无太大差别,需要注意的是Scala中的控制结构大多具有返回值,而其他编程语言中的控制结构一般就仅仅是用于流程控制。本文主要目录如下:

  • 分支结构
    • if-else
    • match-case
  • 循环结构
    • for循环
    • while循环
    • do-while循环
    • break和continue

01 分支结构

分支结构应该编程中最为常用的控制结构了,典型的if-else也是最能代表程序员思维的了。这里首先插一个只有程序员才懂的段子:

女朋友对程序员说:“亲爱的,去超市买一个西瓜吧,如果他们还有鸡蛋,再买20个”,结果程员带了21个西瓜回家。女朋友愤怒地说:“为什么买21个西瓜回来”?程序员答:“因为他们确实有鸡蛋”。

这个冷笑话,其实质就是一个顺序结构 选择分支结构。那么在Scala中,分支结构当然也是用关键字if-else,更具体的说无外乎以下3种形式:

代码语言:javascript复制
scala> val num = 2
val num: Int = 2
// 单分支
scala> if(num>1) print("greater")
greater
// 双分支
scala> if(num>1) print("greater") else print("less")
greater
// 多分支
scala> if(num>2) print("greater") else if(num==2) print("equal") else print("less")
equal

if-else是最常规的分支选择结构,Scala中也不例外。另外,除了以上三种形式外,当然还可以组织嵌套的if-else结构,但实质都是一样。形式虽然一样,但Scala中其实也有其特别之处:那就是Scala中的if-else其实应当理解成一个代码块,而在Scala中但凡是代码块,基本上都对应有返回值,所以无论是单分支、双分支还是多分支,其返回值就是相应分支的结果,虽然这个返回结果可能为Unit,例如上述print语句后其实就并未产生实际的返回值。同时需指出的是,在单分支中只有if单条语句,当条件不满足时实际上也是对应控制的返回结果。

正因为if-else都是有返回值的,所以Scala中并未设立像其他语言中那样的三元选择运算符,而是交由if-else完成这一功能。

除了if-else这种经典的分支结构外,编程语言中另一经典分支结构是switch-case结构,这在Scala中也是有所体现的,只不过未提供switch,而是支持功能更为强大的模式匹配:match-case,甚至有人将其称作是最能体现Scala特色和优势的特性之一。本文作为系列入门文章,仅就其实现最基础的分支结构做以说明。

代码语言:javascript复制
scala> num match{  // match 关键字
         case x if(x<0) => print("<0")  // 代入条件判断
         case 0 => print("0")  // 直接以具体值判断
         case 1 => print("1")
         case _ => print("other") // 用_表示其他情况
       }
other

注:模式匹配在Scala中仍属于一个代码块,也是有返回值的。

在模式匹配中另外值得关注的一个细节是,在各匹配分支后,用映射符号"=>"连接条件和执行逻辑,这与Scala中函数的标志性符号是一致的,都表示映射的含义,一定程度上也暗示着模式匹配其实可理解为根据条件逻辑执行一个个的子函数。

02 循环结构

除了选择分支结构,程序中另外一个常用的循环。实话说,循环常常是在解决很多算法题目时最先想到的方案,虽然效率不高,但却非常简单粗暴和直观易懂。通常情况下,循环有3种结构:

  • for
  • while
  • do……while

Scala也不例外,而结合Scala的特性,这三种循环往往有着更为优雅的运用。

1)for循环。Scala中的for循环其实与Python中的for循环比较类似,通常用法是将一个可迭代对象逐一赋值给循环变量,完成相应操作的过程。最基础的用法如下:

代码语言:javascript复制
scala> for(i <- 1 to 3) println(i)
1
2
3

在for循环内部,还可以直接嵌套逻辑判断条件,术语说法叫做循环守卫,即仅当条件满足时才进入循环体执行,否则进入下一循环:

代码语言:javascript复制
scala> for(i <- 1 to 3 if(i%2==1)) print(i)
1
3

除了嵌套逻辑条件判断,还可以增加一些附属操作,仅仅是为了便于后续循环体执行,对循环执行不产生任何影响:

代码语言:javascript复制
scala> for(i <- 1 to 3; j=2*i) println(j)
2
4
6

注意在循环变量与附属操作之间,需用";"做以分割,表示这是两句代码。而在前述的循环守卫中则可加可不加,且一般情况下不加";"更为直观。

对于嵌套循环,除了类似其他编程语言中的书写两重for循环外,还可直接将两层循环变量写到一个for循环中,功能一致但逻辑更为清晰:

代码语言:javascript复制
// 嵌套for循环常规写法
scala> for(i <- 1 to 3) for(j <- 1 to i) println(i*j)
1
2
4
3
6
9
// 精简写法
scala> for(i <- 1 to 3; j <- 1 to i) println(i*j)
1
2
4
3
6
9

for循环在Scala中仍然属于一个代码块,所以其实也是有返回值的。应用这一特性,for循环其实还有另一个巧妙的运用:由一个迭代器生成另一个迭代器,功能类似于Python中的列表推导式。实现这一功能,需要配合使用关键yield:

代码语言:javascript复制
scala> val nums = (for(i <- 1 to 5) yield i*2).toList
val nums: List[Int] = List(2, 4, 6, 8, 10)

2)while循环。一般而言,对于具有明确次数的循环结构采取for循环比较合适,而对于循环次数未知、需根据循环执行结果判断是否继续执行的情况则选用while循环更为合适。Scala中的while循环与其他编程语言中while用法几乎完全一致:

代码语言:javascript复制
scala> var sum = 0
var sum: Int = 0

scala> var i = 0
var i: Int = 0

scala> while(sum<10){println(i); sum =i; i =1}
0
1
2
3
4

不过,除了使用场景的不同,Scala中while循环与for循环的另一个技术细节上的区别在于:for循环作为一个代码块是有对应返回值的(虽然可能返回值可能为空),而while循环则一定没有返回值(或者说返回值一定为空)。这一区别意味着:for循环既可以用于迭代产生新的迭代结果,也可以依靠历次循环产生相应的效果(即执行循环体带来副作用),而while循环则只能靠循环产生的副作用来实现功能。例如,模拟前面使用for循环 yield产生新的迭代器的过程,强行使用while循环实现如下:

代码语言:javascript复制
scala> val arr = new Array[Int](3)
val arr: Array[Int] = Array(0, 0, 0)

scala> var i = 1
var i: Int = 1

scala> while(i<=3) {arr(i-1) = i*2; i =1}

scala> arr
val res3: Array[Int] = Array(2, 4, 6)

3)do……while循环。do……while循环在实际工作中使用还是比较少的,其使用方法与while循环十分类似,唯一的区别在于while循环是先判断后执行;而do……while循环则是先执行再判断,所以无论如何do……while会至少执行一次。

最后值得指出的是,与其他编程语言不同,在Scala中并没有break和continue两个关键字,即无法简单实现循环中止或者跳过本次循环这一逻辑。虽然取以代之提供了break()方法,但用起来终究还是不够方便。那如果就是要实现break和continue两个需求呢,实际上Scala中可以灵活选用如下3种方式:

  • 增加if条件判断
  • for循环中设置循环守卫
  • while循环中增加相应的判断逻辑

03 小结

控制结构是编写任何程序都不得不涉及到的一个概念,对于常用的分支结构、循环结构在Scala中都有所体现,且均具有一定特色,灵活掌握还是比较方便的。

0 人点赞