快学Scala(第二版)-02-控制结构和函数

2.1 条件表达式

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

if (x>0) 1  else -1

你可以用将if/else表达式的值付给变量:

scala> val x=2
x: Int = 2

scala> val s=if(x>0) 1 else -1
s: Int = 1

这个相当于Java和c++中的?:运算符,上式等同于x>0 ? 1; -1

在Scala中每个表达式都有一个类型

比如上面的例子,1和-1都是Int类型,所以整个表达式的类型就是Int类型。如果两个分支是混合类型,那么表达式的类型是两个分支类型的超类型。举个例子:

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

上述表达式的类型为Any

如果else部分缺失了,比如

if(x>0) 1

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

if(x>0) 1 else ()

你可以把()当作表示“无有用值”的占位符,将Unit当作Java中的void。

从技术上讲,void没有值但是Unit有一个表示“无值”的值。这就好比一个空的钱包和里面有一张写着“没钱”的无面值钞票的钱包之间的区别。

REPE比起编译器来更加“近视”——它在同一时间只能看到一行代码
举例来说,

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

REPL会执行第一行语句,然后显示结果。之后它看到接下来的关键字else就会不知所措。
你可以用花括号来换行:

scala> if(x>0){1
     | }else if(x==0) 0 else -1
res0: Int = 1

只有在REPL中才会有这个顾虑,在编译器中,解析器会自动找到下一行的else。

如果你想在REPL中粘贴成块的代码,可以这样做——键入:paste, CTRL+D结束粘贴模式。

scala> :paste
// Entering paste mode (ctrl-D to finish)

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

// Exiting paste mode, now interpreting.

res3: Int = 1

2.2 语句终止

在Scala中,行尾不需要分号,在}、else以及类似的位置也不需要分号。
不过,如果你想在一行写多个语句,就需要用分号隔开 :

if(n>0) {r=r*n; n-=1}

如果你在写较长的语句,需要分行来写,就要确保第一行以一个不能用作语句结尾的符号结尾的符号结尾。通过茶馆来说一个比较好的选择是操作符:

x=a+x-b*3+
  	c-4

Scala程序员们倾向于使用Kernighan&Ritchie风格的花括号:

if (n>0) {
	r=r*n
	n-=1
}

2.3 块表达式和赋值

在Scala中,{ }块包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。这个特性对于某个val初始化需要多步才能完成的情况很有用。例如:

scala> val distance={val x=2; val y=2;scala.math.sqrt(x*x+y*y)}
distance: Double = 2.8284271247461903

在Scala中,赋值动作本身是没有值的,更严谨地说,它们地值是Unit类型地。前面说过这个类型只有一个值,写作()。

scala> val d={val a=1;val b=2}
d: Unit = ()

由于赋值语句地值是Unit类型的,因此不要连续赋值:

scala> val a=b=1
<console>:7: error: not found: value b

b的值是(),这里无法赋值给a。

2.4 输入和输出

如果要打印一个值,我们用print或者println函数。后者在打印完后会追加一个换行符。
另外还有一个格式化字符串的printf函数:

scala> printf("hello,%s you are %d years old %n","liusong",22)
hello,liusong you are 22 years old

更好的做法是使用字符串插值(string interpolation):

scala> val name="liusong";val age=22
name: String = liusong
age: Int = 22

scala> print(f"hello,$name you are ${age-1}%7.2f years old")  //%7.2f表示宽度为7,精度为2的 浮点数
hello,liusong you are   21.00 years old

你需要用${…}将不是简单的变量名的表达式括起来。

在前缀raw的字符串中,转义序列不会被求值:

scala> println(raw"\n is a new line")
\n is a new line

想要在被插值的字符串中包含$符号,连续写两遍:

scala> val balance=10000
balance: Int = 10000
scala> println(f"hello, you balance is $$$balance")
hello, you balance is $10000

你可以用scala.io.StdIn.readLine方法从控制台读取一行输入,如果是数字/Boolean/或者是字符可以用readInt/readDouble/readByte/readShort/readLong/readFloat/readBoolean或者readChar

scala> val name=scala.io.StdIn.readLine
name: String = LIUSONG

2.5 循环

Scala拥有与Java相同的whiledo循环

while(n>0){
	r=r*n
	n-=1
}

Scala的for循环和Java的不太一样,其用法是:

 	for (i<- 表达式)

让变量i遍历<-右边的表达式的所有值,至于这个遍历如何执行,取决于表达式的类型。对于Range而言,会让i一次取区间中的每一个值:

scala> for(i <- 2 to 8)
     | print(f" $i")
 2 3 4 5 6 7 8

for循环中的变量并没有val或var的指定,该变量的类型是集合类型。作用域一直持续到循环结束。

遍历字符串时,可以用下标值来循环:

scala> val s ="hello"
s: String = hello
scala> for (t<- 0 to s.length-1)
     | println(s(t))
h
e
l
l
o

其实也可以不用下标值,直接遍历字符串序列:

scala> for(ch<- "hello")
     | println(ch)
h
e
l
l
o

Scala中并没有提供break或continue语句来退出循环。如果你需要break时,该怎么办呢/

  • 使用Boolean性的控制变量
  • 使用嵌套函数——你可以从函数当中return
  • 使用Breaks对象中的break方法:
				import scala.util.control.Breaks._
				breakable{
					for(...){
						if(...)break;  //退出breakable块
						....
					}

这里,控制权的转移是通过抛出和捕获异常完成的。因此,如果时间很重要的话,你应该避免使用这种机制。

2.7 高级for循环

你可以以变量<-表达式的形式提供多个生成器,用分号隔开。比如:

scala> for (i<- 1 to 3;j<- 1 to 3) print(f"${10*i+j}%3d")
 11 12 13 21 22 23 31 32 33

每个生成器都可以带上守卫,一个以if开头的Boolean表达式。

scala> for (i<- 1 to 3;j<- 1 to 3 if i!=j) print(f"${10*i+j}%3d")
 12 13 21 23 31 32

注意在if之前没有分号。
你可以使用任意多得定义,引入可以在循环中使用得变量:

scala> for (i<- 1 to 3; from=4-1;j<- from to 3 ) print(f"${10*i+j}%3d")
 13 23 33

如果for循环以yield开始,则该循环会构造出一个集合,每次迭代生成集合中得一个值:

scala> for(i<- 1 to 10)yield i%3
res12: IndexedSeq[Int] = Vector(1, 2, 0, 1, 2, 0, 1, 2, 0, 1)

这类循环叫做for推导式(for comprehension)。生成的集合与它的第一个生成器的类型是兼容的。

scala> for (c<-"hello";i<- 0 to 1) yield (c+i).toChar
res13: IndexedSeq[Char] = Vector(h, i, e, f, l, m, l, m, o, p)

scala> for (i<- 0 to 1 ; c<- "hello") yield (c+i).toChar
res14: IndexedSeq[Char] = Vector(h, e, l, l, o, i, f, m, m, p)

你也可以用这种形式来书写:

for{i <- 1 to 3
	from = 4-i
	j<-from to 3	}

2.7 函数

要定义函数你要给出函数名、参数和函数体,就像这样;

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

你需要给出参数的类型,但不需要给出返回值的类型,Scala的编译器能根据=右边的表岛是类型推断出返回类型。
你也可以用代码块,块中最后一个表达式的值就是返回值类型:

def fac(n:Int)={
	var r =1
	for (i<- 1 to n) r =r*i
	r
}

fac: (n: Int)Int

scala> fac(3)
res16: Int = 6

对于递归函数,我们必须指定返回类型。

scala> def fac(n:Int): Int=if (n<=0) 1 else n*fac(n-1)
scala> fac(4)
res19: Int = 24

如果没有返回类型,Scala编译器就无法校验n*fac(n-1)的类型是Int。

2.8默认参数和带名参数

scala> def decorate (str:String,left="[",right="]")=left+str+right
                                    ^
       error: ':' expected but '=' found.
       
scala> def decorate (str:String,left:String="[",right:String="]")=left+str+right
decorate: (str: String, left: String, right: String)String

scala> decorate("liusong")
res21: String = [liusong]

scala> decorate("liusong","<",">")
res22: String = <liusong>

scala> decorate("liusong",right=">")
res23: String = [liusong>

2.9 变长参数

有时候实现一个可变长度的参数列表很方便:

scala> :paste
// Entering paste mode (ctrl-D to finish)

def sum(args:Int*)={
var result=0
for (arg<- args) result+=arg
result
}

// Exiting paste mode, now interpreting.

sum: (args: Int*)Int

scala> sum(1,3,54,1)
res24: Int = 59

函数得到的是一个类型为Seq的参数。
如果你已经有一个值的序列,你不能直接把它传入参数:

scala> val s =sum(1 to 5)
                    ^
       error: type mismatch;
        found   : scala.collection.immutable.Range.Inclusive
        required: Int

如果sum函数被调用时传入的时单个参数,那么该参数必须是单个整数,而不是一个整数区间。
解决这个 问题的办法时告诉编译器你希望这个参数被当作参数序列处理。追加:_*,就像这样:

scala> val s =sum(1 to 5:_*)
s: Int = 15

在递归定义中我们会用到上述语法:

def recursiveSum(args:Int*): Int={
	if (args.length==0) 0
	else args.head + recursiveSum(args.tail:_*)
}

这里,序列的head是它的首个元素,而tail时所有其他元素的序列。这又是一个Seq,我们用:_*来将它转换成参数序列。

2.10过程

Scala对于不返回值有特殊的表示法,其与函数的区别在于少一个=。这样的“函数”叫做过程(procedure)。过程不返回值,我们调用它仅仅时为了它的副作用

scala> :paste
// Entering paste mode (ctrl-D to finish)

def box(s:String){
    val border="-"*(s.length+2)
    print(f"$border\n|$s|\n$border\n")
}

// Exiting paste mode, now interpreting.

box: (s: String)Unit

scala> box("hello,liusong")
---------------
|hello,liusong|
---------------

2.11 懒值

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

lazy val words=scala.io.Source.fromFile("C://user//....").mkString

这里时Scala的文件操作,现阶段我们只需要相当然的认为这个调用将一个从一个文件读取所有的字符串,并拼接成一个字符串。
如果程序从不访问words,那么文件也不会打开。

2.12 异常

Scala的异常的工作机制和Java是一样的。当你抛出异常时,比如:

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

当前的运算被终止,运行时系统查找可以接受IllegalArgumentException的异常的处理器。控制权将在离抛出点最近的处理器中恢复。如果没有找到符合要求的异常处理器,则程序退出。

和Java一样,抛出的对象必须是java.lang.Throwable的子类。不同的时Scala没有“受检”异常——你不需要声明函数或方法可能会抛出某种异常。
throw表达式有特殊的类型——Nothing。这在if/else语句中很有用。如果一个分支的类型是Nothing,那么if/else表达式的类型就是另一个分支的类型。

scala> val x=(-1)
x: Int = -1

scala> import scala.math._
import scala.math._

//第一个分支是Double型,第二个分支是Nothing类型,所以整个表达式的类型是Double
scala> if (x>0){sqrt(x)
     | }else throw new IllegalArgumentException("x should not be negative")
     
java.lang.IllegalArgumentException: x should not be negative
	at .<init>(<console>:13)
	at .<clinit>(<console>)
	at .<init>(<console>:7)
	at .<clinit>(<console>)
	at $print(<console>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:734)
	at scala.tools.nsc.interpreter.IMain$Request.loadAndRun(IMain.scala:983)
	at scala.tools.nsc.interpreter.IMain.loadAndRunReq$1(IMain.scala:573)
	at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:604)
	at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:568)
	at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:760)
	at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:805)
	at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:778)
	at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:805)
	at scala.tools.nsc.interpreter.ILoop.command(ILoop.scala:717)
	at scala.tools.nsc.interpreter.ILoop.processLine$1(ILoop.scala:581)
	at scala.tools.nsc.interpreter.ILoop.innerLoop$1(ILoop.scala:588)
	at scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala:591)
	at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply$mcZ$sp(ILoop.scala:882)
	at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:837)
	at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:837)
	at scala.tools.nsc.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:135)
	at scala.tools.nsc.interpreter.ILoop.process(ILoop.scala:837)
	at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:83)
	at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
	at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105)
	at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

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

try{.....} catch {........} finally {.........}

fianlly语句不论如何都会被执行,通常是清理工作。

练习

  • 符号函数
scala> def sign(x:Double)={
     | var sign=0
     | if (x>0) sign=1
     | else if (x<0) sign=(-1)
     | else sign=0
     | sign
     | }
sign: (x: Double)Int

scala> sign(-0.9)
res1: Int = -1

scala> sign(0)
res2: Int = 0

scala> sign(9)
res3: Int = 1
  • 一个空的块表达式{ }的值值是(),即”无有用值“,类型是Unit
scala> val r={}
r: Unit = ()

scala> r

scala> r+9
<console>:9: error: type mismatch;
 found   : Int(9)
 required: String
              r+9
                ^
  • 在x为Unit类型,即x=()时,x=y=1是合法的
scala> var x=()
x: Unit = ()

scala> var y=3
y: Int = 3

scala> x=y=1
x: Unit = ()

scala> y
res8: Int = 1

scala> x

  • java循环: for (int i=10; i>=0; i–) System.out.println(i); 的Scala版:
scala> for (i <- 0 to 10) println(10-i)
10
9
8
7
6
5
4
3
2
1
0
  • 生成反向数列
scala> for(i<- 0 to 9 reverse) println(i)
warning: there were 1 feature warning(s); re-run with -feature for details
9
8
7
6
5
4
3
2
1
0
  • 编写一个过程countdown(n :Int),打印从n到0的数字
scala> def countdown(n:Int){
     | for (i<- 0 to n) print(" "+(n-i))
     | }
countdown: (n: Int)Unit

scala> countdown(9)
 9 8 7 6 5 4 3 2 1 0

进阶版:

def countdown(n:Int){
	(0 to n reverse) foreach println 
}
  • 编写一个for循环,计算字符串中所有字母的Unicode代码的乘积
scala> def mul(s:String){
     | var p =1 
     | for (i<- 0 to (s.length-1)) p*=(s(i)+0)
     | print(p)}
mul: (s: String)Unit

scala> mul("Hello")
825152896

\\\\\\\进阶
scala> def uni(s:String){
     | var p:Long=1
     | for(i<-s) p=p*i.toLong
     | print(p)}
uni: (s: String)Unit

scala> uni("Hello")
9415087488
  • 用函数实现上述过程
scala> def uniMul(s:String)={
     | var p=1
     | for(i<- 0 to (s.length-1)) p*=(s(i)+0)
     | p
     | }
uniMul: (s: String)Int

scala> uniMul("Hello")
res65: Int = 825152896
  • 不使用循环,用字符串方法实现
scala> var t:Long = 1
t: Long = 1
 
scala> "Hello".foreach(t *= _.toLong)
 
scala> t
res15: Long = 9415087488
  • 不使用循环,使用递归来实现
def product(s:String):Long={
    if(s.length == 1) return s.charAt(0).toLong
    else s.take(1).charAt(0).toLong * product(s.drop(1))
}
  • 编写函数计算xn
    分析
    xn = x*x(n-1),如果n是正数的话
    x0 = 1
    xn = 1/x(-n),如果n是负数的话
scala> :paste
// Entering paste mode (ctrl-D to finish)

def mi(x:Double,n:Int):Double={
    if(n == 0) 1
    else if(n>0 ) x * mi(x,n-1)
    else 1/mi(x,-n)
}

// Exiting paste mode, now interpreting.

mi: (x: Double, n: Int)Double

scala> mi(2,3)
res0: Double = 8.0

scala> mi(2,4)
res1: Double = 16.0

scala> mi(2,-3)

思考,
递归和循环有什么不同

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值