《快学Scala》——控制结构和函数
条件表达式
- 在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。
- 在Scala中每个表达式都有一个类型。如:
if (x > 0) 1 else -1
的类型是Int,因为两个分支的类型是Int。当两个分支的类型不同时,那该表达式的类型就是两个分支类型的公共超类型。注:Scala里,每个类都继承自通用的名为Any的超类。因为所有的类都是Any的子类,所以定义在Any中的方法就是“共同的”方法:它们可以被任何对象调用。Scala还在层级的底端定义了一些类,如Null和Nothing,扮演通用的子类。即,Any是所有其他类的超类,Nothing是所有其他类的子类。
- 如果else部分缺失,比如
if (x > 0) 1
,那么该表达式可能没有输出值。但是在Scala中,每个表达式都应该有值。所以在Scala中引入了Unit类,写做()。那么上面的表达式就等同于if (x > 0) 1 else ()
。
块表达式和赋值
- 在Scala中,{}块包含一系列表达式,其结果也是一个表达式。块的最后一个表达式的值就是块的值。如下:
val z = {val x = 2; val y = 4; x * y} //结果z = x * y = 8
- 在Scala中,赋值动作本身是没有值的,或者更严格的说他们的值是Unit类型的。如
{r = r * n; n -= 1}
的值就是Unit类型的。 注: 由于赋值语句的值是Unit类型的,故不能将它们串联起来。如x = y = 1
,这里y = 1
的值是()
。
输入和输出
- 输出:用print和println这和Java类似。
- 输入:可以用readLine函数读取控制台上的一行输入。也可以用readInt、readDouble、readByte、readShort、readLong、readFloat、readBoolean或者readChar。和其他方法的区别是,readLine带有一个参数作为提示字符串。如下:
val name = readLine("Your name:") print("Your age:") val age = readInt()
循环
Scala中的使用循环有两种选择:
- while:
while (n > 0) { r = r * n n -= 1 }
- for:
for(i <- 表达式)
for(i <- 1 to n) r = r * i
注: 在
for
循环的变量之前并没有val
和var
的指定。该变量的类型是集合的元素类型。循环变量的作用域一直持续到循环结束。 - 当
for
循环在遍历字符串和数组时,需要使用until
方法而不是to
方法。to
方法返回包含上限的闭区间,until
方法返回一个不包含上限的区间。 如下遍历字符串的例子://方法一 val s = "Hello" var sum = 0 for(i <- 0 until s.length) sum +=s(i) //方法二 var sum = 0 for(ch <- "Hello") sum += ch
注: Scala不提供break
或continue
来退出循环,退出循环的方法如下:
- 使用
Boolean
型的控制变量。 - 使用嵌套函数,从函数中
return
。 - 使用
Breaks
对象的break
方法。
高级for循环和for推导式
- 可以使用变量<-表达式的形式提供多个生成器,用分号将它们隔开。例如:
for(i <- 1 to 3; j <- 1 to 3) print((10 * i + j) + " ") //将打印11 12 13 21 22 23 31 32 33
- 每个生成器都可以带一个守卫,以
if
开头的Boolean
表达式。注意if
前没有分号例如:for(i <- 1 to 3; j <- 1 to 3 if i != j) print((10 * i + j) + " ") //将打印12 13 21 23 31 32
- 如果
for
循环的循环体使用yield
开始,则该循环会构造出一个集合,每次迭代生成集合中的一个值。这类循环成为for推导式。例如:for(i <- 1 to 10) yield i % 3 //生成 Vector(1, 2, 0, 1, 2, 0, 1, 2, 0, 1)
其中
for
推导式生成的集合与它的第一个生成器是类型兼容的。如下图:
函数
函数是带参数的表达式。 匿名函数:
scala> (x: Int) => x + 1
res5: Int => Int = $$Lambda$1061/1068809905@34cb1310
=>
的左边是参数列表,右边是一个包含参数的表达式。
将函数赋值给一个变量:
scala> val add = (x: Int, y: Int) => x + y
add: (Int, Int) => Int = $$Lambda$1062/1069736192@31885b4b
scala> add(1,2)
res6: Int = 3
方法
定义语法
def 方法名 (参数列表) : 返回类型 = 方法体
必须给定所有参数的类型,只要方法不是递归方法,就不用指定返回类型。如果函数体需要多个表达式,可以使用代码块,块中最后一个表达式的值就是函数的返回值。
scala> def add(x:Int, y:Int) = x + y
add: (x: Int, y: Int)Int
scala> add(1, 2)
res4: Int = 3
函数 VS 方法
区别
- 方法和函数定义语法不同
- 方法一定定义在类、特质或
object
中 - 方法一定可以共享所在类、特质或
object
中的属性 - 可以调用函数,也可以存放在一个变量中,作为参数传递给其他函数或方法,也可以作为返回值
联系
- 可以把函数作为参数传递给一个方法
def m(f:(Int, Int) => Int) = f(5, 1) val f = (x:Int, y:Int) => x - y println(m(f)) //输出:4
- 方法可以转换成函数
- 把一个方法作为参数传递给其他的方法或函数
- 使用
方法名 _
把一个方法显式的转换成一个函数
scala> def m(f:(Int, Int)=>Int)=f(5,1) m: (f: (Int, Int) => Int)Int scala> val f=(x:Int,y:Int)=>x-y f: (Int, Int) => Int = $$Lambda$1040/1765745171@7b5021d1 scala> m(f) res0: Int = 4 scala> def m2(x:Int,y:Int)=x-y m2: (x: Int, y: Int)Int scala> m(m2) res1: Int = 4 scala> m2 _ res2: (Int, Int) => Int = $$Lambda$1059/1257785974@b5ff70b scala> m(m2 _) res3: Int = 4
默认参数和带名参数
def decorate(str: String, left: String = "[", right:String = "]") = left + str + right
//调用函数如下
decorate("Hello") //输出 [Hello]
decorate("Hello", "%%%") //输出 %%%Hello]
decorate(left = "[", str = "Hello", right = "]") //输出 [Hello]
decorate("Hello",right = "****[") //输出 [Hello***]
decorate("<<","Hello",right = ">>>") //输出 Hello<<>>>
从上例可以看出left和right是默认参数,如果不喜欢默认值就可以在调用的时候重新赋值。我们可以在提供参数值时带上参数名,这个时候参数列表的顺序可以变。当我们混用未命名参数和带名参数时,需要将未命名参数排在前面,并将未命名参数按照参数列表的顺序排放。
过程
Scala中如果函数体包含在花括号当中但没有前面的=
号,那么返回类型应该是Unit
。这样的函数被称作过程。
懒值
当val
被声明为lazy
时,它的初始化将被推迟,直到首次使用时才取值。