1. 程式人生 > >Scala的內建控制結構

Scala的內建控制結構

Scala中的內建控制機構僅有if、while、for、try、match和函式呼叫。雖然Scala的基礎控制結構少,但也足以支援指令式語言裡所有的實質內容。因為它們都能產生值,有助於縮短程式碼。
程式設計師可以通過使用返回值的控制結構簡化程式碼,避免建立臨時變數來儲存控制結構中的計算結果。

1 If表示式

1.1.常規式

var filename="default"
if(!args.isEmpty)
  filename=args(0)

1.2.scala里根據條件做初始化的例子

val filename=
  if(!args.isEmpty)  args(0
) else "default.txt"

這樣寫有兩個好處:
一是使用val體現了函式式風格,並具有java的final變數類似的效果。它告訴讀者,程式碼變數將不再改變,從而節省了他們審查變數作用域的所有程式碼和檢查它是否改變的工作。
二是更好地支援等效推論。在表示式沒有副作用的前提下,引入的變數等效於計算它的表示式。無論何時都可以用表示式替換變數名。

儘可能使用val,它能讓你的程式碼既容易閱讀又容易重構。

2.While迴圈

2.1常規式

while迴圈包括狀態判斷和迴圈體,只要狀態保持為真,迴圈體一遍遍被執行。

def gcdLoop(x:Long, y:Long):Long={
  var
a=x var b=y while(a!=0) { val temp = a a = b%a b=temp } b }

例如Do-While迴圈

var line =""
do {
  line=readLine()
  println("Read: "+line)
} while(line!="")

while和do-while結構之所以稱之為“迴圈”,而不是表示式,因為他們不能產生有意義的結果,結果型別只能是Unit型別,是表明存在且唯一存在型別為Unit的值,寫成()。()的存在是scala的Unit與Java的void不同的地方,例如

def
greet() {println("hi")} greet()==()

以上程式碼將返回true。因為greet返回unit值()。所有greet的結果和unit值()相等,返回true。

另外需要注意的是,賦值等式本身也是unit值
例如

var line=""
while((line=readLine())!="")  //不起作用
  println("Read: "+line)

因為賦值語句line=readLIne()的值將永遠返回()而不是”“

建議你在程式碼中更為審慎地使用while迴圈。如果對while或do迴圈沒有非用不可的理由,請嘗試別的方式實現同樣的功能。

3.For表示式

scala的For表示式是列舉操作的瑞士軍刀。

3.1列舉集合類

val filesHere=(new java.io.File(".")).listFiles
for(file<- filesHere)
  println(file)

通過被稱為發生器語法“file<-filesHere”,遍歷了filesHere陣列的元素。

for表示式語法對任何種類的集合類都有效,而不只是陣列。例如Range

for(i<-1 to 4)
  println("Iteration "+i)

以下這種方式遍歷陣列不常用:

for(i<-0 to filesHere.length-1)
  println(filesHere(i))

不常用的原因是由於集合本身可以直接被列舉,並且不會出現溢位。

3.2 過濾

又是不想列舉處全部元素,只想過濾出某個子集。這可以通過for表示式的括號中新增過濾器(filter),即if子句。例如過濾以.scala為結尾的檔名:

val filesHere = (new java.io.File(".")).listFiles
for(file<-filesHere if file.getName.endsWith(".scala"))
  println(file)

也可以加入更多的過濾器,只要不斷新增if子句即可。如果發生器中超過一個過濾器,if子句必須用分號分隔。

for(
  file<-fileHere
  if file.isFile;
  if file.getName.endsWith(".scala")
) println(file)

3.3巢狀列舉

如果加入多個<-子句,就可以巢狀迴圈。例如:

def fileLines(file:java.io.File)=
  scala.io.Source.fromFile(file).getLines.toList

def grep(pattern:String)=
  for(
    file<-fileHere
    if file.getName.endsWith(".scala")
    line<-fileLines(file)
    if line.trim.mathches(pattern)
  ) println(file+": "+line.trim)

grep(".*gcd.*")

3.4 流間變數繫結

在前面的程式碼中重複出現了表示式line.trim。如果想要只計算一遍,可以通過等號=把結果繫結到新的變數。繫結變數被當做val引入和使用,但是不帶關鍵字val。

def grep(pattern:String)=
  for(
    file<-fileHere
    if file.getName.endsWith(".scala")
    line<-fileLines(file)
    trimmed=line.trim
    if trimmed.mathches(pattern)
  ) println(file+": "+trimmed)

grep(".*gcd.*")

3.5 製造新的集合

到現在為止所有例子只是對列舉值進行操作然後釋放,除此以為,還可以建立一個值去記住每一次的迭代,只要在for表示式之前加上關鍵字yield。比如:

def scalaFiels=
  for{
    file<-filesHere
    if file.getName.endsWith(".scala")
  } yield file

for表示式在每次執行的時候都會產生一個新值,本例中是file。當for表示式完成的時候,結果將是包含了產生值的集合物件。物件的型別基於列舉子句處理的集合型別。本例中的結果為Array[File],因為filesHere是陣列並且產生的表示式型別是File。
另外注意yield一定放在左括號之前,而不是程式碼塊的最後一個表示式之前。

for {子句} yield {迴圈體}

val forLineLengths=
  for {
    file<- filesHere
    if file.getName.endsWith(".scala")
    line<-fileLines(file)
    trimmed = line.trim
    if trimmed.matches(".*for.*")
  } yield trimmed.length

4使用try表示式處理異常

Scala中方法處理能返回值以外,還可以通過丟擲異常中止執行。方法的呼叫者要麼可以捕獲並處理這個異常,或者也可以簡單的終止掉,並把異常上升到呼叫者的呼叫者處。

4.1丟擲異常

throw也是有結果型別的表示式,例如:

val half=
 if (n%2==0)
   n/2
 else 
   throw new RuntimeException("n must be even")

異常丟擲的型別是Nothing。
像這樣的例子經常使用。if的一個分支計算值,另一個爆出異常並得出Nothing。

4.2捕獲異常

在scala中捕獲異常時,在catch子句中經常使用case模式匹配,這是scala的特色。模式匹配是一種很強大的特徵。

import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
try{
  val f= new FileReader("inpit.txt")
}  catch{
  case ex:FileNotFoundException=>//處理丟失的檔案
  case ex:IOException=> //處理其他的IO錯誤

Scala裡不需要捕獲檢查異常,或者把他們生命在throws子句中。

4.3 finally子句

如果要讓某些程式碼無論如何都要執行的話,可以放在finall子句中。例如:

import java.io.FileReader

val file=new FileReader("input.txt")
try{
  //使用檔案
}finally {
  file.close()  //確保關閉檔案
}

在Scala中還可以使用另一種被稱為出借模式(loan pattern)的技巧更簡潔地達到同樣的目的。

def withPrintWriter(file:File,op:PrintWriter=>Unit) {
  val writer= new PrintWriter(file)
  try {
    op(writer)
  } finally {
    writer.close()
  }
}

上面的程式碼中,由withPrintWriter而並非使用者程式碼,確認檔案在結尾被關閉。因為不可能忘記關閉檔案。因為控制抽象函式,打開了資源並借貸給函式。當用戶函式完成時,它發出訊號說明它不再需要借的資源,於是資源被關閉在finally中,以確信其確實被關閉。

4.4生成值

try-catch-finally也產生值,但是finally子句應當做關閉檔案之類的清理工作,他們不應該修改主函式體或catch子句中計算的值。

scala與java的try-finally最大的不同是,在java中finally子句不產生值。

def f():Int=try{return 1} finally {return 2}
def g():Int=try{1} finally{2}

呼叫f()返回2,呼叫g()返回1。

5.匹配表示式

scala中的match類似於java中的swith,但是他可以在提供多個備選項中做出選擇。預設情況下用_說明。例如:

val firstArg = if(args.length<0) args(0) else ""

firstArg match{
  case "salt"=> println("pepper")
  case "chips"=> println("salsa")
  case "eggs"=> println("bacon")
  case _ =>println("huh?")

與Java相比,Scala的匹配表示式還有一些重要差別。
(1)任何型別的常量,或者其他東西都可以成為case,而不只是java中的整數型別和列舉常量。
(2)每個備選項的最後沒有break,因為break是隱含的。
(3)match也能產生值。例如:

val firstArg = if(!args.isEmpty) args(0) else ""

val firend= firstArg match{
  case "salt"=> println("pepper")
  case "chips"=> println("salsa")
  case "eggs"=> println("bacon")
  case _ =>println("huh?")
}
println (frined)