Scala学习(二)--- 控制结构和函数

控制结构和函数java

摘要c++

本篇主要学习在Scala中使用条件表达式循环函数,你会看到Scala和其余编程语言之间一个根本性的差别。在Java或C++中,咱们把表达式(好比3+4)和语句(好比if语句)看作两样不一样的东西。表达式有值,而语句执行动做。在Scala中,几乎全部构造出来的语法结构都有值。这个特性使得程序更加精简,也更易读。本篇的要点包括:程序员

1. 表达式有值算法

2. 块也有值,是它最后一个表达式的值编程

3. Scala的for循环就像是"加强版"的Java for循环数组

4. 分号(在绝大多数状况下)不是必需的安全

5. void类型是Unit数据结构

6. 避免在函数定义中使用return编程语言

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

8. 异常的工做方式和Java或C++中基本同样,不一样的是你在catch语句中使用"模式匹配"

9. Scala没有受检异常

条件表达式

表达式的值

Scala的if/else语法结构,和Java或C++-样。不过,在Scala中if/else表达式有值,这个值就是跟在if或else以后表达式的值。例如:

if (x > 0) 1 else-1

上述表达式的值是1或-1,具体是哪个取决于x的值。你能够将if/else表达式的值赋值给变量

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

这与以下语句的效果同样:

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

不过,第一种写法更好,由于它能够用来初始化一个val。而在第二种写法当中,S必须是var

Java和C++有一个 ? : 操做符用于一样目的。以下表达式

x > 0 ? 1: -1 // Java或c++

等同于Scala表达式if(x>0) 1 else -1。不过,你不能在 ? : 表达式中插入语句。Scala的if/else将在Java和C++中分开的两个语法结构if/else? :结合在了一块儿

表达式的类型

在Scala中,每一个表达式都有一个类型。举例来讲,表达式

if(x > 0) 1 else-1

上述表达式的类型是lnt,由于两个分支的类型都是Int。混合类型表达式,好比:

if (x > 0) "positive" else -1

上述表达式的类型是两个分支类型的公共超类型。在本例中,其中一个分支是java.lang.String,而另外一个分支是lnt。它们的公共超类型叫作Any 。若是else部分缺失了,好比:

if (x > 0) 1

那么有可能该语句没有输出值。可是在Scala中,每一个表达式都应该有某种值。这个问题的解决方案是引入一个Unit类,写作()。不带else的这个if语句等同于:

if (x > 0) 1 else ()

你能够把()当作是表示"无有用值"的占位符,将Unit当作Java或C++中的void。从技术上讲,void没有值可是Unit有一个表示"无值"的值。若是你必定要深究的话,这就比如空的钱包和里面有一张写着"没钱"的无面值钞票的钱包之间的区别

块表达式和赋值

块表达式

在java或C++中,块语句是一个包含于{}中的语句序列。每当你须要在逻辑分支或循环中放置多个动做时,你均可以使用块语句。在 Scala中,{}块包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值

这个特性对于那种对某个val的初始化须要分多步完成的状况颇有用。例如:

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

{}块的值取其最后一个表达式,在此处以红色字体标出。变量dx和dy仅做为计算所须要的中间值,很干净地对程序其余部分而言不可见了。

赋值

在Scala中,赋值动做自己是没有值的或者更严格地说,它们的值是Unit类型的。你应该还记得,Unit类型等同于Java和C++中的void,而这个类型只有一个值,写作()。一个以赋值语句结束的块,好比

{ r=r*n;n-=1;}

上述块表达式的值是Unit类型。这没有问题,只是当咱们定义函数时须要意识到这一点。因为赋值语句的值是Unit类型的,别把它们串接在一块儿:

x=y=1 //别这样作

y=1的值是(),你几乎不太可能想把一个Unit类型的值赋值给x。而在Java和C++中,赋值语句的值是被赋的那个值。在这些语言中,将赋值语句串接在一块儿是有意义的。

输入输出

输出

若是要打印一个值,咱们用print或println函数。后者在打印完内容后会追加一个换行符。举例来讲

print ("Answer : ")

println (42)

与下面的代码输出的内容相同:

println("Answer: "+42)

另外,还有一个带有C风格格式化字符串的printf函数:

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

输入

你能够用readLine函数从控制台读取一行输入。若是要读取数字Boolean或者是字符,能够用readlnt、readDouble、 readByte、readShort、readLong、readFloat、readBoolean或者readChar。与其余方法不一样,readLine带一个参数做为提示字符串:

val name=readLine ("Your name: ")

print("Your age:")

val age=readlnt()

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

循环

While循环

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

while (n>0) {

r=r*n

n-=1

}

for循环

Scala没有与for ( 初始化变量;检查变量是否知足某条件;更新变量 ) 循环直接对应的结构。若是你须要这样的循环,有两个选择:一是使用while循环,二是使用以下for句:

for (i <- 1 to n)

r=r*i

经过Richlnt类的这个to方法,1 to n这个调用返回数字1到数字n(含)的Range(区间)。下面的这个语法结构

for (i <- 表达式)

让变量i遍历<- 右边的表达式的全部值。至于这个遍历具体如何执行,则取决于表达式的类型。对于Scala集合好比Range而言,这个循环会让i依次取得区间中的每一个值。

遍历字符串数组时,你一般须要使用从0到n-1的区间。这个时候你能够用util方法而不是to方法。util方法返回一个并不包含上限的区间

val s="Hello"

var sum=0

for (i <- 0 util s.length)//i的最后一个取值是s.length -1

sum+=s(i)

在本例中,事实上咱们并不须要使用下标。你能够直接遍历对应的字符序列:

var sum=0

for (ch <- "Hello" )

sum+=ch

在Scala中,对循环的使用并不如其余语言那么频繁。一般咱们能够经过对序列中的全部值,应用某个函数的方式来处理它们,而完成这项工做只须要一次方法调用便可

高级for循环和for推导式

在Scala中,for循环比起Java和C++的功能要丰富得多,下面将介绍其高级特性

生成器

你能够以变量 <- 表达式的形式提供多个生成器,用分号将它们隔开。例如:

for(i <- 1 to 3;j <- 1 to 3){

print(10*i+j+"\t") //将打印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+"\t") //将打印12 13 21 23 31 32

}

注意在if以前并无分号。

引入定义

除此以外,你可使用任意多的定义,引入能够在循环中使用的变量:

for(i <- 1 to 3;form=4-i;j <- form to 3 ){

print(10*i+j+"\t") //将打印13 22 23 31 32 33

}

for 推导式
若是for循环的循环体以yield开始,则该循环会构造出一个集合,每次迭代生成集合中的一个值:

for(i <- 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)

函数

普通函数

Scala除了方法外还支持函数。方法对对象进行操做,函数不是。C++也有函数,不过在Java中咱们只能用静态方法来模拟。要定义函数,你须要给出函数的名称参数函数体,就像这样:

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

必须给出全部参数的类型。不过,只要函数不是递归的,你就不须要指定返回类型。Scala编译器能够经过=符号右侧的表达式的类型推断出返回类型。若是函数体须要多个表达式完成,能够用代码块。块中最后一个表达式的值就是函数的返回值。举例来讲,下面的这个函数返回位于for循环以后的r的值。

def fac(n:Int) = {

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

}

在本例中咱们并不须要用到return。咱们也能够像Java或C++那样使用retum,来当即从某个函数中退出,不过在Scala中这种作法并不常见。

递归函数

对于递归函数,咱们必须指定返回类型。例如:

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

若是没有返回类型,Scala编译器没法校验n*fac(n - 1)的类型是Int。某些编程语言(如ML和Haskell)可以推断出递归函数的类型,用的是Hindley-Milner算法。不过,在面向对象的语言中这样作并不老是行得通。如何扩展Hindley-Milner算法让它可以处理子类型仍然是个科研命题。

默认参数和带名参数

指定默认参数

咱们在调用某些函数时并不显式地给出全部参数值,对于这些函数咱们可使用默认参数。例如:

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

left+str+right

这个函数有两个参数,left和right,带有默认值"["和"]"。若是你调用decorate("Hello"),你会获得" [Hello]"。若是你不喜欢默认的值,能够给出你本身的版本:

decorate("Hello","<<<",">>>")

若是相对参数的数量,你给出的值不够,默认参数会从后往前逐个应用进来。举例来讲:

decorate("Hello",">>>[")

则会使用right自带的默认参数,获得:"<<<[Hello] "。

指定参数名

你也能够在提供参数值的时候指定参数名。例如:

decorate (left="<<<", str ="Hello", right=">>>")

结果是"<Hello>>>",由上可知带名参数并不须要跟参数列表的顺序彻底一致。带名参数可让函数更加可读。它们对于那些有不少默认参数的函数来讲也颇有用。

混用参数名

固然,也能够混用未命名参数带名参数,只耍那些未命名的参数排在前面的便可:

decorate ("Hello",right="]<<<")

上面代码,将调用decorate ("Hello","[" ,"]<<<")

变长参数

有时候,实现一个能够接受可变长度参数列表的函数会更方便。如下示例显示了它的语法:

def sum (args : Int*)={

var result=0

for (arg <- args)

result +=arg

result

}

那么,可使用任意多的参数来调用该函数

val s=sum (1, 4, 9, 16, 25)

函数获得的是一个类型为Seq参数,可使用for循环来访问每个元素。

若是你已经有一个值的序列,则不能直接将它传入上述函数。举例来讲,以下的写法是不对的:

val s =sum(1 to 5)

若是sum函数被调用时传人的是单个参数,那么该参数必须是单个整数,而不是一个整数区间。解决这个问题的办法是告诉编译器你但愿这个参数被当作参数序列处

理。追加: _*,就像这样:

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

递归定义当中咱们会用到上述语法:

def recursiveSum (args: Int*): Int:{

if (args.length==0)

0

else

args.head+recursiveSum( args.tail : _* )

}

在这里,序列的head是它的首个元素,而tail是全部其余元素的序列,这又是一个Seq,咱们用:_*来将它转换成参数序列

过程

Scala对于不返回值的函数有特殊的表示法。若是函数体包含在花括号当中但没有前面的=号,那么返回类型就是Unit。这样的函数被称作过程(procedure),过程不返回值,咱们调用它仅仅是为了它的反作用。举例来讲,以下过程把一个字符串打印在一个框中,就像这样:

---------

|Hello|

---------

因为过程不返回任何值,因此咱们能够略去;号。

def box(s:String) { //注意前面没有=

var border="-" * s.length+"--\n"

println(border+"|"+s+"|\n"+border)

}

有人不喜欢用这种简明的写法来定义过程,并建议你们老是显式声明Unit返回类型:

def box (s: String): Unit=(

}

懒值

val被声明为lazy时,它的初始化将被推迟,直到咱们首次对它取值。例如

lazy val words=scala.io.Source.fromFile("/usr/share/dict /words").mkString

这个调用将从一个文件读取全部字符并拼接成一个字符串, 若是程序从不访问words,那么文件也不会被打开。为了验证这个行为,咱们能够在REPL中试验,但故意拼错文件名。在初始化语句被执行的时候并不会报错。不过,一旦你访问words,就将会获得一个错误提示:文件未找到。

懒值对于开销较大的初始化语句而言十分有用。它们还能够应对其余初始化问题,好比循环依赖。更重要的是,它们是开发懒数据结构的基础。你能够把懒值当作是介于val和def的中间状态。对好比下定义:

val words =scala.io.Source.fromFile ("/usr/share/dict/words") .mkString // 在words被定义时即被取值

lazy val words=scala.jo.Source.fromFile("/usr/share/dict/words").mkString // 在words被首次使用时取值

def words=scala.io.Source.fromFile("/usr/share/dict/words") .mkString // 在每一次words被使用时取值

须要注意的是:懒值并非没有额外开销。咱们每次访问懒值,都会有一个方法被调用,而这个方法将会以线程安全的方式检查该值是否已被初始化。

异常

Scala异常机制

Scala异常的工做机制和Java或C++ 样,当你抛出异常时,好比:

throw new IllegalArgumentException("x should not be neqativen")

当前的运算被停止,运行时系统查找能够接受IllegaIArgumentException的异常处理器,控制权将在离抛出点最近的处理器中恢复。若是没有找到符合要求的异常处理器,则程序退出。

和Java样,抛出的对象必须是java.lang.Throwable的子类。不过,与Java不一样的是,Scala没有"受检"异常,即你不须要声明说函数或方法可能会抛出某种异常。在Java中, "受检"异常编译期被检查。若是你的方法可能会抛出IOException,你必须作出声明。这就要求程序员必须去想那些异常应该在哪里被处理掉,这是个值得称道的目标。不幸的是,它同时也催生出怪兽般的方法签名,好比void doSometing() throws IOException,InterruptedException,ClassNotFoundException。许多Java程序员很反感这个特性,最终过早捕获这些异常,或者使用超通用的异常类。Scala的设计者们决定不支持"受检"异常,由于他们意识到完全的编译期检查并不老是最好的。

throw表达式有特殊的类型Nothing。这在if/else表达式中颇有用。若是一个分支的类型是Nothing,那么if/else表达式的类型就是另—个分支的类型。举例来讲,考虑以下代码:

if (x > 0){

sqrt(x)

else

throw new IllegalArgumentException("x should not be negative")

第一个分支类型是Double,第二个分支类型是Nothing。所以,if/else表达式的类型是Double。

Scala异常捕捉

捕获异常的语法采用的是模式匹配的语法

try{

process (new URL( "http: //horstmann.com/fred-tiny. gif"))

}catch{

case _: MalformedURLException=>println("Bad URL: "+url)

case ex: IOException=>ex.printStacKTrace()

}

和Java或C++样,更通用的异常应该排在更具体的异常以后。并且,若是你不须要使用捕获的异常对象,可使用_来替代变量名。

try/finally释放资源

try/finally语句让你能够释放资源,不论有没有异常发生。例如:

var in=new URL("http://horstmann.com/fred.gif") .openStream))

try{

process{in)

}finally{

in. close()

}

finally语句不论process函数是否抛出异常都会执行,reader总会被关闭。这段代码有些微妙,也提出了一些问题:

■ 若是URL构造器openStream方法抛出异常怎么办,这样一来try代码块和finally语句都不会被执行。这没什么很差,in从未被初始化,所以调用close方法没

有意义

■ 为何val in=new URL(...).openStream()不放在try代码块里,由于这样作的话in的做用域不会延展到finally语句当中

■ 若是in.close()抛出异常怎么办,这样一来异常跳出当前语句,废弃替代掉全部先前抛出的异常。这跟Java同样,并非很完美,理想状况是老的异常应

该与新的异常一块儿保留

除此以外,try/catch和try/finally的目的是互补的。try/catch语句处理异常,而try/finally语句在异常没有被处理时执行某种动做,一般是清理工做。咱们能够把它们结合在一块儿成为单个try/catch/finally语句:

try { …} catch {…} finally { …}

 

若是,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
若是,您但愿更容易地发现个人新博客,不妨点击一下左下角的【关注我】。
若是,您对个人博客所讲述的内容有兴趣,请继续关注个人后续博客,我是【Sunddenly】。

本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。

相关文章
相关标签/搜索