所谓的内建控制结构是指编程语言中可以使用的一些代码控制语法,如Scala中的if, while, for, try, match, 以及函数调用等。需要注意的是,Scala几乎所有的内建控制结构都会返回一个值,这是由于函数式编程语言被认为是计算值的过程,所以作为函数式编程语言的一个组件,这些内建控制结构也不例外。

  如果不好理解函数式编程语言中每一个内建控制结构都会返回一个值这一概念,可以回想一下? :表达式,这个表达式基本上能表明这一概念,作用和if表达式类似,但是会根据条件得到一个分支的值作为返回值。

一、if表达式

  Scala的if语句和其他语言中的类似,传入一个判断条件。

  比如,在指令式语言中,我们常常这样写

  1. var filename = "default.txt"
  2. if (!args.isEnpty)
  3. filename = args(0)

  上面的代码中,首先定义一个变量filename并赋给一个初始化值。如果满足if中的判断条件,则将该值进行更新。

  在Scala中,可以对上面的代码进行简化,不在需要定义一个变量并且根据条件更新其值。

  1. val filename =
  2. if (!args.isEmpty) args(0)
  3. else "default.txt"

  if表达式会返回一个值,根据判断条件决定该返回值及类型取自if表达式的哪一个分支。并且当该表达式没有副作用时,直接使用该表达式得到的值和定义一个val变量并赋值的作用是相同的。所以,如果只需要将该文件名打印出来,可以进一步简化成

  1. println(if (!args.isEmpty) args(0) else "default.txt")

  不过,一般建议还是先将该值赋给一个变量,然后输出该变量的值。这样能够保证代码的可读性和重构性。

二、while循环

1、while循环

  while循环有一个判断条件和一个循环体,只要满足判断条件,该循环体就会被执行。下面展示一段求最大公约数的while循环代码

  1. def gcdLoop(x: Long, y: Long): Long = {
  2. var a = x
  3. var b = y
  4. while (a != 0) {
  5. val temp = a
  6. a = b % a
  7. b = temp
  8. }
  9. b
  10. }

2、do-while循环

  同样,Scala也提供一个do-while循环结构,和上面这个不同的是,do-while循环首先执行循环体,然后判断循环条件是否满足,以决定下一次循环是否继续。下面这段代码,循环读取输入文本,直到遇到空行为止。

  1. val line = ""
  2. do {
  3. line = readLine()
  4. print("Read: " + line)
  5. } while (line != "")

3、Unit返回值

  while和do-while都被称为循环,而不是表达式。这时由于while和do-while循环不会得到一个返回值,或者是它们的返回值为Unit。Unit值被写成(),如下所示,定义一个返回值为Unit的函数greet

  1. def greet() {println("hi")}
  2. greet() == ()

  结果如下,表示greet函数的返回值与()是相等的。

  

  需要注意的是,在Scala中对一个var变量重新赋值的语句得到的返回值也是Unit。

  比如,如果执行下面这段代码,Scala编译器会认为将一个Unit类型的值与""对比,结果永远为true

  1. var line = ""
  2. while ((line = readLine()) != "")
  3. println("Read: " + line)

  运行结果如下,即使某一次无任何输入,该循环仍然会执行并继续。

  

  

  对于纯函数式编程语言来说,其中是没有whiledo-while循环这一概念的。但是Scala提供了这一功能,while循环的使用,使得代码可读性更强。而如果不使用while循环的话,有些代码会写出递归调用的形式,比如下面采用递归的方法计算最大公约数。

  1. def gcd(x: Long, y: Long): Long =
  2. if (y == 0) x else gcd(y, x % y)

三、for表达式

  for表达可以用于简单的字面变量以及集合类型变量,也可以在遍历时使用一些过滤条件进行过滤,还能够基于旧的集合生成新的集合对象。

1、枚举集合类型

  下面这段代码,获取当前路径下所有的文件,并打印出来。其中filesHere变量是一个数组类型的变量,使用file <- filesHere循环遍历数组中的每一个元素。

  1. val filesHere = (new java.io.File(".")).listFiles
  2. for (file <- filesHere)
  3. println(file)

  运行结果如下所示,

  

  

  上面展示的是数组类型变量的for表达式,同样的for表达式还可以用于更多其他类型的变量遍历上。比如

  1. for (i <- 1 to 4)
  2. println("Iteration " + i)

  结果如下

  

2、增加过滤条件

  也可以在for表达式中增加一个过滤条件,筛选出其中满足条件的元素进行处理。比如下面这段代码,也是列举出当前路径下的所有文件,但是只显示其中.scala类型的文件。

  1. val filesHere = (new java.io.File(".")).listFiles
  2. for (file <- filesHere if file.getName.endsWith(".scala"))
  3. println(file)

  结果如下:

  

  在循环判断中,也可以增加多个判断条件。

  1. for (
  2. file <- filesHere
  3. if file.isFile
  4. if file.getName.endsWith(".scala")
  5. ) println(file)

3、嵌套循环

  如果使用多个<-表达式,就会得到一个嵌套循环结构。例如下面代码中有一个两层嵌套。外层循环遍历filesHere遍历,内层循环遍历fileLines方法读取到的文件中每一行的内容。

  1. def fileLines(file: java.io.File) =
  2. scala.io.Source.fromFile(file).getLines().toList
  3. def grep(pattern: String) =
  4. for {
  5. file <- filesHere
  6. if file.getName.endsWith(".scala")
  7. line <- fileLines(file)
  8. if line.trim.matches(pattern)
  9. } println(file + ": + line.trim)
  10. grep(".*gcd.*")

  注意这里将for表达式的圆括号换成了花括号。

4、流间变量绑定

  上面那段嵌套循环的代码中,重复执行了line.trim代码。如果不想重复的话,可以将line.trim的结果通过=符合赋值给一个val变量,这个变量的val关键字可以省略不写。

  在下面这段代码中,trimmed变量在for表达式中被引入,并且初始化为line.trim。接下来的代码中两次使用到了这个trimmed变量,一次是在if表达式中,一次是在println操作中。

  1. def grep (pattern: String) =
  2. for {
  3. file <- filesHere
  4. if file.getName.endsWith(".scala")
  5. line <- fileLines(file)
  6. trimmed = line.trim
  7. if trimmed.matches(pattern)
  8. } println(file + ":" + trimmed)
  9. grep(".*gcd.*")

  在原文中,这一小节的英语表述是(Mid-stream variable bindings)。 这里的流间变量绑定是指在for表达式中引入的变量,可以在for表达式的后续其他代码中使用该变量。

5、生成新的集合变量

  之所以这里称for为表达式,而不是for循环,是因为for表达式可以获得一个返回值。需要使用到关键字yield

  

(1)生成源集合相同类型的新集合

  例如下面的代码,找出当前路径下所有的.scala类型的文件,并赋值给一个function。该for表达式函数体每一次执行,都会产生一个file值。当filesHere遍历完成之后,会将所有yield出来的file值存储到同一个集合类型对象中。返回集合的元素类型,与源集合中的元素类型相同。

  1. def scalaFiles =
  2. for {
  3. file <- filesHere
  4. if file.getName.endsWith(".scala")
  5. } yield file

  结果如下,

  

  

  上面这段代码中需要注意的是yield关键字的位置。正确的格式应该是下面这样的,

  1. for clauses yield body

  由于上面代码中的函数体比较简单,看不出有什么异常,如果按照下面这种写法,就是错误的

  1. for ( file <- filesHere if file.getName.endsWith(".scala") {
  2. yield file
  3. }

  这种错误写法中,yield关键字就是写入了函数体中。

(2)生成其他类型的集合

  其实在Scala的for表达式中,也可以生成与源集合不同类型的集合。比如下面这段代码中,首先获取当前路径下的所有.scala文件,然后对每一个文件读取其中的所有行,去重,然后取出其中只包含for关键字的代码,最后,获取这几行代码的字符个数。

  其中的fileLines函数得到一个Iterator[String]类型的返回值。

  1. val forLineLengths =
  2. for {
  3. file <- filesHere
  4. if file.getName.endsWith(".scala")
  5. line <- fileLines(file)
  6. trimmed = line.trim
  7. if trimmed.matches(".*for.*")
  8. } yield trimmed.length

  总结一下,有关于for表达式,可以很方便的遍历某个集合,也可在遍历时加入过滤条件对循环的集合进行筛选。同时也可以在for表达式中多次写入<-实现嵌套循环,并且,for表达式中引入的变量可以省略val关键字并在后续多次使用。最后,面对复杂的for表达式时,最好不用圆括号,而改为使用花括号。

四、try表达式异常处理

  Scala中的表达式,除了正常执行并得到一个返回值的情况外,还有一种是运行时出现异常。出现异常时,程序可能会被终止,也可以由方法调用者捕获并对该异常作出处理。

1、抛出异常

  Scala抛出异常和Java类似,同样适用throw关键字,抛出的这个异常类,也可以由开发者自定义。

  1. throw new IllegalArgumentException

  对于throw表达式,Scala其实也对应了一个返回值,其类型为Nothing。

  比如下面求一个偶数的一半,当传入的n是偶数时才进行计算,否则直接抛出一个RuntimeException。

  1. val half = (
  2. if (n % 2 == 0)
  3. n / 2
  4. else
  5. throw new RuntimeException("n must be even"))

  运行结果如下,当n为3时,直接抛出一个异常,而当n为4时,返回一个Int值2。

  

  前面提到,if表达式返回值的类型为两个分支计算值的公共父类,而现在throw表达式的的返回值为Nothing,并且在Scala的类层级关系中Nothing是任何类的直接或间接子类。也就是说,对于if表达式来说,某一个分支是throw表达式的话,那么该if表达式最终返回值的类型是有值计算的那一个分支的类型。

2、捕获异常

  抛出了异常,如果不加处理,程序就会终止。抛出异常后,可以使用catch关键字捕获该异常,并针对不同的异常作出不同的处理方案。下面的代码展示了一个完成的try...catch代码结构

  1. import java.io.FileReader
  2. import java.io.FileNotFoundException
  3. import java.io.IOException
  4. try {
  5. val f = new FileReader("input.txt")
  6. // Use and close file
  7. } catch {
  8. case ex: FileNotFoundException => // Handle missing file
  9. case ex: IOException => // Handle other I/O error
  10. }

  当抛出异常时,会顺序执行catch代码块中的逻辑。上面代码中,如果抛出FileNotFoundException,那么就会执行文件不存在这一分支的代码。而如果抛出IOException异常,就会执行I/O异常这一部分的逻辑。而如果这两种异常都匹配不到抛出的异常时,程序还是会终止。

3、finally子句

  类似于Java代码中,有时候遇到抛出异常而导致程序终止时,可能还希望执行一些收尾的逻辑。比如下面这段代码中,如果读取文件时抛出异常导致程序终止,那么最好在终止前将打开的文件关闭。关闭文件的这段逻辑就写在finally子句中。

  1. import java.io.FileReader
  2. val file = new FileReader("input.txt")
  3. try {
  4. // Use the file
  5. } finally {
  6. file.close() // Be sure to close the file
  7. }

4、产生一个返回值

  类似于其他的控制结构,try-catch-finally代码也会产生一个返回值。

  比如下面代码中,对一个URL地址进行处理,如果不抛出异常,那么使用传入的地址构造一个URL对象。如果抛出异常并被捕获到的话,则使用默认地址构造一个URL对象。

  1. import java.net.URL
  2. import java.net.MalformedURLException
  3. def urlFor(path: String) =
  4. try {
  5. new URL(path)
  6. } catch {
  7. case e: MalformedURLException =>
  8. new URL("http://www.scala-lang.org")
  9. }

  如果抛出异常并没有被捕获到,那么返回的结果为Nothing。而finally子句中设置的返回值是无法获取到的,这也是由于程序终止时一般在finally中只需要做一些收尾的操作,而不需要去改变某些变量的值。

  如果在finally中显示的return了某个返回值,或者抛出一个异常,那么这个返回值或者抛出的异常会将之前try-catch中应有的返回值覆盖掉。比如

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

  结果如下:

  

  而不显示返回一个值时,最终的返回值为try中的内容。比如

  1. def g(): Int = try {1} finally {2}

  结果如下:

  

五、match表达式

  match表达式类似于switch表达式,可以在多个待选值中进行选择。

  看下面的代码,如果args的长度大于0,那么firstArg变量的值为第一个元素,否则为空字符串。然后,根据firstArg变量的具体内容,如果是salt则打印paper,如果是eggs则打印bacon。而如果不是这三种中的任何一个,则打印huh?。下面代码中的下划线_表示任意匹配。

  1. val firstArg = if (args.length > 0) args(0) else ""
  2. firstArg match {
  3. case "salt" => println("pepper")
  4. case "chips" => println("salsa")
  5. case "eggs" => println("bacon")
  6. case _ => println("huh?")
  7. }

  和Java中的switch不同的是,Scala中的match语法可以匹配包括整数,字符串等任意类型的变量,并且在每个case后没有break关键字。而switchmatch最大的不同是,match表达式也会有一个返回值。上面的代码片段中,打印每一种情况的显示内容,而接下来这一段代码,会根据匹配情况将字符串赋值给一个变量。

  1. val firstArg = if (!args.isEmpty) args(0) else ""
  2. val friend =
  3. firstArg match {
  4. case "salt" => "pepper"
  5. case "chips" => "salsa"
  6. case "eggs" => "bacon"
  7. case _ => "huh?"
  8. }
  9. println(friend)

  可以从下面的结果看到,friend的值为salsa

  

Programming In Scala笔记-第七章、Scala中的控制结构的更多相关文章

  1. Android群英传笔记——第七章:Android动画机制和使用技巧

    Android群英传笔记--第七章:Android动画机制和使用技巧 想来,最 近忙的不可开交,都把看书给冷落了,还有好几本没有看完呢,速度得加快了 今天看了第七章,Android动画效果一直是人家中 ...

  2. JVM学习笔记-第七章-虚拟机类加载机制

    JVM学习笔记-第七章-虚拟机类加载机制 7.1 概述 Java虚拟机描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被 ...

  3. 2018-11-27 中文代码示例之Programming in Scala笔记第七八章

    续前文: 中文代码示例之Programming in Scala学习笔记第二三章 中文代码示例之Programming in Scala笔记第四五六章. 同样仅节选有意思的例程部分作演示之用. 源文档 ...

  4. Getting Started With Hazelcast 读书笔记(第七章)

    第七章 部署策略 Hazelcast具有适应性,能根据不同的架构和应用进行特定的部署配置,每个应用可以根据具体情况选择最优的配置: 数据与应用紧密结合的模式(重点,of就是这种) 胖客户端模式(最好用 ...

  5. [Python学习笔记][第七章Python文件操作]

    2016/1/30学习内容 第七章 Python文件操作 文本文件 文本文件存储的是常规字符串,通常每行以换行符'\n'结尾. 二进制文件 二进制文件把对象内容以字节串(bytes)进行存储,无法用笔 ...

  6. o'Reill的SVG精髓(第二版)学习笔记——第七章

    第七章:路径 所有描述轮廓的数据都放在<path>元素的d属性中(d是data的缩写).路径数据包括单个字符的命令,比如M表示moveto,L表示lineto.接着是该命令的坐标信息. 7 ...

  7. 《图解HTTP》阅读笔记--第七章---确保WEB安全的HTTPS

    第七章.确保WEB安全的HTTPSHTTP的缺点:通信使用明文(不加密),内容可能会被窃听 解决---加密处理: //将通信加密 :通过SSL(安全套接层)---HTTPS(超文本传输安全协议)--- ...

  8. C++ primer plus读书笔记——第14章 C++中的代码重用

    第14章 C++中的代码重用 1. 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现).获得接口是is-a关系的组成部分.而使用组合,类可以获得实现,但不能获得接口. ...

  9. Programming In Scala笔记-第六章、函数式对象

    这一章主要是以定义和完善一个有理数类Rational为线索,分析和介绍有关类定义,构造函数,方法重写,变量定义和私有化,以及对操作符的定义等. 一.Rational类定义和构造函数 1.定义一个空类 ...

随机推荐

  1. Java高级篇(二)——网络通信

    网络编程是每个开发人员工具相中的核心部分,我们在学习了诸多Java的知识后,也将步入几个大的方向,Java网络编程就是其中之一. 如今强调网络的程序不比涉及网络的更多.除了经典的应用程序,如电子邮件. ...

  2. PostgreSQL 常用系统自带方法

    数据库字符编码问题:    -- 查看PostgreSQL数据库服务器端编码:    show server_encoding;    -- 查看PostgreSQL客户端工具psql编码:    s ...

  3. 创建类似于Oracle中decode的函数

    -- 创建类似于Oracle中decode的函数create or replace function decode(variadic p_decode_list text[])returns text ...

  4. 教你从手机中提取system镜像制作线刷救砖包的简单方法

    其实在制作刷机包的过程中,有时候没有官方或者第三方提供的救砖包(线刷),那怎么办?常规的方法有两种:(此处为常规方法,回读的方式暂不说明)     1.卡刷包转线刷包     2.dd命令导出分区镜像 ...

  5. [Luogu 3835]【模板】可持久化平衡树

    Description 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作(对于各个以往的历史版本): 插入x数 删除x数(若有多个相同的数,因只删除一个,如果没有请忽略该操作 ...

  6. [TJOI 2013]拯救小矮人

    Description 一群小矮人掉进了一个很深的陷阱里,由于太矮爬不上来,于是他们决定搭一个人梯.即:一个小矮人站在另一小矮人的 肩膀上,知道最顶端的小矮人伸直胳膊可以碰到陷阱口.对于每一个小矮人, ...

  7. [ZJOI2008]泡泡堂

    题目描述 第XXXX届NOI期间,为了加强各省选手之间的交流,组委会决定组织一场省际电子竞技大赛,每一个省的代表队由n名选手组成,比赛的项目是老少咸宜的网络游戏泡泡堂.每一场比赛前,对阵双方的教练向组 ...

  8. [HNOI2006]公路修建问题

    题目描述 输入输出格式 输入格式: 在实际评测时,将只会有m-1行公路 输出格式: 输入输出样例 输入样例#1: 复制 4 2 5 1 2 6 5 1 3 3 1 2 3 9 4 2 4 6 1 3 ...

  9. bzoj 4919: [Lydsy六月月赛]大根堆

    Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切地说,你需要选择尽可能多的节点,满足大根堆的性质: ...

  10. CSAPP-程序优化

    代码移动: 如果一个表达式总是得到同样的结果,最好把它移动到循环外面,这样只需要计算一次.编译器有时候可以自动完成,比如说使用 -O1 优化.一个例子: void set_row(double *a, ...