《快学Scala》——控制结构和函数

2019-03-16

条件表达式

  • 在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的值是()

输入和输出

  • 输出:用printprintln这和Java类似。
  • 输入:可以用readLine函数读取控制台上的一行输入。也可以用readIntreadDoublereadBytereadShortreadLongreadFloatreadBoolean或者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循环的变量之前并没有valvar的指定。该变量的类型是集合的元素类型。循环变量的作用域一直持续到循环结束。

  • 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不提供breakcontinue来退出循环,退出循环的方法如下:

  1. 使用Boolean型的控制变量。
  2. 使用嵌套函数,从函数中return
  3. 使用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 方法

区别

  1. 方法和函数定义语法不同
  2. 方法一定定义在类、特质或object
  3. 方法一定可以共享所在类、特质或object中的属性
  4. 可以调用函数,也可以存放在一个变量中,作为参数传递给其他函数或方法,也可以作为返回值

联系

  1. 可以把函数作为参数传递给一个方法
     def m(f:(Int, Int) => Int) = f(5, 1)
     val f = (x:Int, y:Int) => x - y
     println(m(f)) //输出:4
    
  2. 方法可以转换成函数
    • 把一个方法作为参数传递给其他的方法或函数
    • 使用方法名 _把一个方法显式的转换成一个函数
     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<<>>>

从上例可以看出leftright默认参数,如果不喜欢默认值就可以在调用的时候重新赋值。我们可以在提供参数值时带上参数名,这个时候参数列表的顺序可以变。当我们混用未命名参数和带名参数时,需要将未命名参数排在前面,并将未命名参数按照参数列表的顺序排放。

过程

Scala中如果函数体包含在花括号当中但没有前面的=号,那么返回类型应该是Unit。这样的函数被称作过程

懒值

val被声明为lazy时,它的初始化将被推迟,直到首次使用时才取值。

参考文献

【Scala】Scala的类层级