每天半小时掌握Scala(day 02)
Posted 爱上终身学习
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每天半小时掌握Scala(day 02)相关的知识,希望对你有一定的参考价值。
===控制结构===
scala和其他编程语言有一个根本性差异:在scala中,几乎所有构造出来的语法结构都有值。这个特性使得程序结构更加精简。scala内建的控制结构很少,仅有if、while、for、try、match和函数调用等而已。如此之少的理由是,scala从语法层面上支持函数字面量。
if表达式:
scala的if/else语法结构与java等一样,但是在scala中if/else表达式有值,这个值就是跟在if/esle后边的表达式的值。如下:
val s = if(x > 0) 1 else -1
同时注意:scala的每个表达式都有一个类型,比如上述if/esle表达式的类型是Int。如果是混合类型表达式,则表达式的类型是两个分支类型的公共超类型。String和Int的超类型就是Any。如果一个if语句没有else部分,则当if条件不满足时,表达式结果为Unit。如:
if(x > 0) 1
就相当于:
if(x > 0) 1 else ()
while循环:
scala拥有与java和c++中一样的while和do-while循环,while、do-while结果类型是Unit。
for表达式:
scala中没有类似于for(; ; )的for循环,你可以使用如下形式的for循环语句:
for(i <- 表达式)
该for表达式语法对于数组和所有集合类均有效。具体介绍如下:
枚举:for(i <- 1 to 10),其中“i <- 表达式”语法称之为发生器,该语句是让变量i(注意此处循环变量i是val的(但无需你指定),该变量的类型是集合的元素类型)遍历表达式中的所有值。1 to 10产生的Range包含上边界,如果不想包含上边界,可以使用until。
过滤:也叫守卫,在for表达式的发生器中使用过滤器可以通过添加if子句实现,如:for(i <- 1 to 10 if i!=5),如果要添加多个过滤器,即多个if子句的话,要用分号隔开,如:for(i <- 1 to 10 if i!=5; if i!=6)。
嵌套枚举:如果使用多个“<-”子句,你就得到了嵌套的“循环”,如:for(i <- 1 to 5; j <- 1 to i)。
流间变量绑定:你可以在for发生器以及过滤器等中使用变量保存计算结果,以便在循环体中使用,从而避免多次计算以得到该结果。流间变量绑定和普通变量定义相似,它被当作val,但是无需声明val关键字。
制造新集合:for(…) yield 变量/循环体,最终将产生一个集合对象,集合对象的类型与它第一个发生器的类型是兼容的。
实际上:for表达式具有等价于组合应用map、flatMap、filter和foreach这几种高阶函数的表达能力。实际上,所有的能够yield(产生)结果的for表达式都会被编译器转译为高阶方法map、flatMap及filter的组合调用;所有的不带yield的for循环都会被转译为仅对高阶函数filter和foreach的调用。正是由于这几个高阶函数支持了for表达式,所以如果一个数据类型要支持for表达式,它就要定义这几个高阶函数。有些时候,你可以使用for表达式代替map、flatMap、filter和foreach的显式组合应用,或许这样会更清晰明了呢。
scala中没有break和continue语句。如果需要类似的功能时,我们可以:
1) 使用Boolean类型的控制变量
2) 使用嵌套函数,你可以从函数当中return
3) ...
match表达式与模式匹配:
scala中没有switch,但有更强大的match。它们的主要区别在于:
① 任何类型的常量/变量,都可以作为比较用的样本;
② 在每个case语句最后,不需要break,break是隐含的;
③ 更重要的是match表达式也有值;
④ 如果没有匹配的模式,则MatchError异常会被抛出。
match表达式的形式为:选择器 match { 备选项 }。一个模式匹配包含了一系列备选项,每个都开始于关键字case。每个备选项都包含了一个模式以及一到多个表达式,它们将在模式匹配过程中被计算。箭头符号“=>”隔开了模式和表达式。按照代码先后顺序,一旦一个模式被匹配,则执行“=>”后边的表达式((这些)表达式的值就作为match表达式的值),后续case语句不再执行。示例如下:
a match {
case 1 => "match 1"
case _ => "match _"
}
match模式的种类如下:
① 通配模式:可以匹配任意对象,一般作为默认情况,放在备选项最后,如:
case _ =>
② 变量模式:类似于通配符,可以匹配任意对象,不同的是匹配的对象会被绑定在变量上,之后就可以使用这个变量操作对象。所谓变量就是在模式中临时生成的变量,不是外部变量,外部变量在模式匹配时被当作常量使用,见常量模式。注意:同一个模式变量只能在模式中出现一次。
③ 常量模式:仅匹配自身,任何字面量都可以作为常量,外部变量在模式匹配时也被当作常量使用,如:
case "false" => "false"
case true => "truth"
case Nil => "empty list"
对于一个符号名,是变量还是常量呢?scala使用了一个简单的文字规则对此加以区分:用小写字母开始的简单名被当作是模式变量,所有其他的引用被认为是常量。如果常量是小写命名的外部变量,那么它就得特殊处理一下了:如果它是对象的字段,则可以加上“this.”或“obj.”前缀;或者更通用的是使用字面量标识符解决问题,也即用反引号“`”包围之。
④ 抽取器模式:抽取器机制基于可以从对象中抽取值的unapply或unapplySeq方法,其中,unapply用于抽取固定数量的东东,unapplySeq用于抽取可变数量的东东,它们都被称为抽取方法,抽取器正是通过隐式调用抽取方法抽取出对应东东的。抽取器中也可以包含可选的apply方法,它也被称作注入方法,注入方法使你的对象可以当作构造器来用,而抽取方法使你的对象可以当作模式来用,对象本身被称作抽取器,与是否具有apply方法无关。样本类会自动生成伴生对象并添加一定的句法以作为抽取器,实际上,你也可以自己定义一个任意其他名字的单例对象作为抽取器使用,以这样的方式定义的抽取器对象与样本类类型是无关联的。你可以对数组、列表、元组进行模式匹配,这正是基于抽取器模式的。
⑤ 类型模式:你可以把类型模式当作类型测试和类型转换的简易替代,示例如下:
case s: String => s.length
⑥ 变量绑定:除了独立的变量模式之外,你还可以把任何其他模式绑定到变量。只要简单地写上变量名、一个@符号,以及这个模式。
模式守卫:模式守卫接在模式之后,开始于if,相当于一个判断语句。守卫可以是任意的引用模式中变量的布尔表达式。如果存在模式守卫,只有在守卫返回true的时候匹配才算成功。
Option类型:scala为可选值定义了一个名为Option的标准类型,一个Option实例的值要么是Some类型的实例,要么是None对象。分离可选值最通常的办法是通过模式匹配,如下:
case Some(s) => s
case None => “?”
模式无处不在:在scala中,模式可以出现在很多地方,而不单单在match表达式里。比如:
① 模式使用在变量定义中,如下:
val myTuple = (123, “abc”)
val (number, string) = myTuple
② 模式匹配花括号中的样本序列(即备选项)可以用在能够出现函数字面量的任何地方,实质上,样本序列就是更普遍的函数字面量,函数字面量只有一个入口点和参数列表,样本序列可以有多个入口点,每个都有自己的参数列表,每个样本都是函数的一个入口点,参数被模式所特化。如下:
case Some(x) => "is int"
case None => "?"
}
③ for表达式里也可以使用模式。示例如下:
for((number, string) <- myTuple) println(number + string)
模式匹配中的中缀标注:带有两个参数的方法可以作为中缀操作符使用,使用中缀操作符时实际上是其中一个操作数在调用操作符对应的方法,而另一个操作数作为方法的参数。但对于模式来说规则有些不同:如果被当作模式,那么类似于p op q这样的中缀标注等价于op(p,q),也就是说中缀标注符op被用做抽取器模式。
===函数(上)===
函数定义:
定义函数时,除了递归函数之外,你可以省略返回值类型声明,scala会根据=号后边的表达式的类型推断返回值类型,同时=号后边表达式的值就是函数的返回值,你无需使用return语句(scala推荐你使用表达式值代替return返回值,当然根据你的需要,也可以显式使用return返回值)。示例如下:
def abs(x: Double) = if(x >= 0) x else -x
def fac(n: Int) = {
var r = 1
for(i <- 1 to n) r = r * i
r
}
对于递归函数必须指定返回值类型,如下:
def fac(n: Int) : Int = if(n <= 0 ) 1 else n * fac(n-1)
但你要知道的是:声明函数返回类型,总是有好处的,它可以使你的函数接口清晰。因此建议不要省略函数返回类型声明。
函数体定义时有“=”时,如果函数仅计算单个结果表达式,则可以省略花括号。如果表达式很短,甚至可以把它放在def的同一行里。
去掉了函数体定义时的“=”的函数一般称之为“过程”,过程函数的结果类型一定是Unit。因此,有时定义函数时忘记加等号,结果常常是出乎你的意料的。
没有返回值的函数的默认返回值是Unit。
函数调用:
scala中,方法调用的空括号可以省略。惯例是如果方法带有副作用就加上括号,如果没有副作用就去掉括号。如果在函数定义时,省略了空括号,那么在调用时,就不能加空括号。另外,函数作为操作符使用时的调用形式参见相应部分。
函数参数:
一般情况下,scala编译器是无法推断函数的参数类型的,因此你需要在参数列表中声明参数的类型。对于函数字面量来说,根据其使用环境的不同,scala有时可以推断出其参数类型。
scala里函数参数的一个重要特征是它们都是val(这是无需声明的,在参数列表里你不能显式地声明参数变量为val),不是var,所以你不能在函数里面给参数变量重新赋值,这将遭到编译器的强烈反对。
重复参数:
在scala中,你可以指明函数的最后一个参数是重复的,从而允许客户向函数传入可变长度参数列表。要想标注一个重复参数,可在参数的类型之后放一个星号“*”。例如:
def echo(args: String*) = for(arg <- args) println(arg)
这样的话,echo就可以被零至多个String参数调用。在函数内部,重复参数的类型是声明参数类型的数组。因此,echo函数里被声明为类型“String*”的args的类型实际上是Array[String]。然而,如果你有一个合适类型的数组,并尝试把它当作重复参数传入,会出现编译错误。要实现这个做法,你需要在数组名后添加一个冒号和一个_*符号,以告诉编译器把数组中的每个元素当作参数,而不是将整个数组当作单一的参数传递给echo函数,如下:
echo(arr: _*)
默认参数与命名参数:
函数的默认参数与java以及c++中相似,都是从左向右结合。另外,你也可以在调用时指定参数名。示例如下:
def fun(str: String, left: String = “[”, right: String = “]”) = left + str + right
fun(“hello”)
fun(“hello”, “<<<”)
fun(“hello”, left = “<<<”)
每天18:00向你推送最实用的干货,欢迎关注~
点击这里查看往期精彩内容:
以上是关于每天半小时掌握Scala(day 02)的主要内容,如果未能解决你的问题,请参考以下文章