Skip to content

Latest commit

 

History

History
407 lines (355 loc) · 7.97 KB

第七章 内置控制结构.md

File metadata and controls

407 lines (355 loc) · 7.97 KB

第七章 内置控制结构

只有if, while, for, try, match. 更多在库中. 只有while无返回值.

7.1 if表达式

命令式风格:

var 文件名 = "默认.txt"
if (!参数.isEmpty)
  文件名 = 参数[0]

改为if, 也避免了var:

val 文件名 =
  if (!参数.isEmpty) 参数[0]
  else "默认.txt"

这样某些情况下可以省去变量:

println(if (!参数.isEmpty) 参数[0] else "默认.txt")

建议尽量用val, 方便阅读也易于重构

7.2 while循环

def 最大公约数循环(x: Long, y: Long): Long = {
  var a = x
  var b = y
  while (a != 0) {
    val 临时 = a
    a = b % a
    b = 临时
  }
  b
}

do-while循环读行至行为空:

var  = ""
do {
  行 = readLine()
  println("读: " + 行)
} while (行 != "")

while结构不是表达式, 因为它们只有Unit返回值. 下面演示它与void的不同:

scala> def 问好() = { println("嗨") }
问好: ()Unit

scala> () == 问好()
<console>:13: warning: comparing values of types Unit and Unit using `==' will always yield true
       () == 问好()
          ^
嗨
res0: Boolean = true

下面的赋值也返回unit值:

var  = ""
while ((行 = readLine()) != "") // () != "" 总为真, 因此无限循环
  println("读: " + 行)

上一章的最大公约数是递归实现, 建议尽量用它代替while循环. while循环中也常见var的使用, 或I/O.

7.3 for表达式

遍历

val 此处文件 = (new java.io.File(".")).listFiles

for (文件 <- 此处文件)
  println(文件)

遍历i从1到4

scala> for (i <- 1 to 4)
     |   println("迭代" + i)
迭代1
迭代2
迭代3
迭代4

不包括上限:

scala> for (i <- 1 until 4)
     |   println("迭代" + i)
迭代1
迭代2
迭代3

第一个文件遍历例子也可这样写, 但不常见:

for (i <- 0 until 此处文件.length)
  println(此处文件(i))

因为并不需要i, 也更易于出错

过滤

val 此处文件 = (new java.io.File(".")).listFiles
           
for (文件 <- 此处文件 if 文件.getName.endsWith(".scala"))
  println(文件)

等同于:

val 此处文件 = (new java.io.File(".")).listFiles
           
for (文件 <- 此处文件)
  if (文件.getName.endsWith(".scala"))
    println(文件)

也支持多个过滤条件:

for (
  文件 <- 此处文件
  if 文件.isFile
  if 文件.getName.endsWith(".scala")
) println(文件)

嵌套迭代 下面等价于两层for遍历

def 从文件读行(文件: java.io.File) =
  scala.io.Source.fromFile(文件).getLines.toList

def 匹配文本(模式: String) =
  for (
    文件 <- 此处文件
    if 文件.getName.endsWith(".scala");
    行 <- 从文件读行(文件)
    if 行.trim.matches(模式)
  ) println(文件 + ": " + 行.trim)

匹配文本(".*gcd.*")

临时(Mid-stream)变量绑定 上面例子中.trim调用两次. 下面用一个变量避免, 前面省略val

为何用{}? -- 因为有额外的赋值语句

def 从文件读行(文件: java.io.File) =
  scala.io.Source.fromFile(文件).getLines.toList

def 匹配文本(模式: String) =
  for {
    文件 <- 此处文件
    if 文件.getName.endsWith(".scala");
    行 <- 从文件读行(文件)
    已修剪 = 行.trim
    if 已修剪.matches(模式)
  } println(文件 + ": " + 已修剪)

匹配文本(".*gcd.*")

生成新集合

def scala文件 =
  for {
    文件 <- 此处文件
    if 文件.getName.endsWith(".scala")
  } yield 文件

注意格式为for 子句 yield 块. 不能将yield置于for之内

上一节的例子改为生成int数组:

val for行长度 =
  for {
    文件 <- 此处文件
    if 文件.getName.endsWith(".scala");
    行 <- 从文件读行(文件)
    已修剪 = 行.trim
    if 已修剪.matches(".*for.*")
  } yield 已修剪.length

7.4 try表达式处理异常

抛异常

throw new IllegalArgumentException

throw的返回类型为Nothing, 详见11.3

val 一半 =
  if (n % 2 == 0)
    n / 2
  else
    throw new RuntimeException("n需为偶数")

捕捉异常

import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException

try {
  val 文件 = new FileReader("输入.txt")
} catch {
  case 异常: FileNotFoundException => // 处理无此文件情况
  case 异常: IOException => // 处理其他I/O错误
}

注意与Java不同, Scala不需用throws声明所有异常

finally子句

import java.io.FileReader

val 文件 = new FileReader("输入.txt")
try {
  // 使用文件
} finally {
  文件.close()
}

loan模式见9.4

产生一个值 catch可有返回值:

import java.net.URL
import java.net.MalformedURLException

def 取url(路径: String) =
  try {
    new URL(路径)
  } catch {
    case e: MalformedURLException =>
      new URL("某路径")
  }

finally如果显式返回一个值, 将会覆盖try或catch中返回的值, f()返回2:

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

如果隐式返回一个值, 则不会覆盖, g()返回1:

def g(): Int = try 1 finally 2

由于不很想当然, 建议finally仅用于善后的副作用操作, 如关闭文件

7.5 match表达式

基本功能类似于switch, 但match可用户匹配任意模式(详见15章)

val 首参数 = if (参数.length > 0) 参数[0] else ""

首参数 match {
  case "甜酱" => println("辣酱")
  case "羊肉" => println("泡馍")
  case "皮蛋" => println("豆腐")
  case _ => println("啥?")
}

与swtich的最大区别是, match有返回值:

val 首参数 = if (参数.length > 0) 参数[0] else ""

val 伙伴 =
  首参数 match {
    case "甜酱" => println("辣酱")
    case "羊肉" => println("泡馍")
    case "皮蛋" => println("豆腐")
    case _ => println("啥?")
  }
println(伙伴)

7.6 没有break和continue怎么过

Java例程:

int i = 0;
boolean 找到了 = false;
while (i < 参数.length) {
    if (参数[i].startsWith("-")) {
        i = i + 1;
        continue;
    }
    if (参数[i].endsWith(".scala")) {
        找到了 = true;
        break;
    }
    i = i + 1;
}

对应Scala:

var i = 0
var 找到了 = false

while (i < 参数.length && !找到了) {
  if (!参数(i).startsWith("-")) {
    if (参数(i).endsWith(".scala"))
      找到了 = true
  }
  i = i + 1
}

如想避免var, 用递归:

def 开始搜(i: Int): Int =
  if (i >= 参数.length) -1
  else if (参数(i).startsWith("-")) 开始搜(i + 1)
  else if (参数(i).endsWith(".scala")) i
  else 开始搜(i + 1)

val i = 开始搜(0)

尾递归优化详见8.9

如果一定要使用break, 有scala.util.control提供

7.7 变量作用域

def 乘法表() = {
  var i = 1
  // 仅i
  
  while (i <= 10) {
    var j = 1
    // i和j
    
    while (j <= 10) {
      var 乘积 = (i * j).toString
      // i, j, 乘积
      var 长度 = 乘积.length
      // i, j, 乘积, 长度
      
      while (长度 < 4) {
        print(" ")
        长度 += 1
      }
      
      print(乘积)
      j += 1
    }
    
    // i, j; 乘积, 长度已出作用域
    println()
    i += 1
  }
  // 仅i
}

与Java一个不同是, 允许在嵌套作用域中使用同名变量, 下面先打印2后打印1:

val a = 1;
{
  val a = 2
  println(a)
}
println(a)

交互环境中, 每个声明都在一个新嵌套的作用域中:

scala> val a = 1
a: Int = 1

scala> val a = 2
a: Int = 2

scala> println(a)
2

可看成:

val a = 1;
{
  val a = 2
  {
    println(a)
  }
}

7.8 重构命令风格代码

改为函数式风格:

def 创建行序列(: Int) =
  for (列 <- 1 to 10) yield {
    val 乘积 = (行 * 列).toString
    val 缩进 = " " * (4 - 乘积.length)
    缩进 + 乘积
  }

def 创建行(: Int) = 创建行序列(行).mkString

def 乘法表() = {
  val 表序列 =
    for (行 <- 1 to 10)
    yield 创建行(行)

  表序列.mkString("\n")
}

(第七章完)