本文链接:Scala学习(六)——运算符
简介
在任何语言里,运算符都是很重要的部分,Scala也不例外。然而,从某种意义上讲,Scala根本就没有除了直接赋值运算符=
以外的运算符。这是怎么回事儿呢?接下来就开始讲。
本质
在Scala中,除了=
以外,运算符其实都是方法。中缀运算符是一个参数的方法,前缀运算符和后缀运算符是无参方法。在其他语言中我们熟悉的表达式到了Scala中可能实质上只是一系列方法调用。比如:
1 2 |
val a = 1 + 2 // 等价于 1.+(2) val b = 1 + 2 * 3 // 等价于 1.+(2.*(3)) |
在Scala学习(三)中说明for
循环的时候提到过一个例子:
1 2 3 |
for(n <- 0 to 10) { println(n) } |
其中0 to 10
就是类似于运算符的一种方法调用,其实质上是0.to(10)
。当然,这里还包含了一个隐式转换,以后再讲。
方法和运算符运算没有本质的界限,方法调用也可以用类似运算符的方式完成:
1 2 3 4 5 6 |
class AClass { def look(a: Int): Unit = println("Look " + a) } val I = new AClass val around = 8 I look around // 一个文字游戏,输出 Look 8 |
重载
Scala中的运算符重载很简单,不需要多余的关键字,只要把运算符放在方法名的位置上就好了:
1 2 3 4 |
class Vec2(val x: Int, val y: Int) { def +(o: Vec2) = new Vec2(x + o.x, y + o.y) def *(o: Vec2) = x * o.x + y * o.y } |
重载运算符可以是任意长度,但是只能由符号或者字母中的一类构成($
和_
算作字母)。比如<:<
是合法的,但是<a<
是不合法的。
前缀运算符只支持四种:~
!
+
-
,分别用unary_
作为前缀来定义:
1 2 3 4 |
class Vec2(val x: Int, val y: Int) { def unary_- = new Vec2(-x, -y) def unary_+ = this } |
后缀运算符支持并非默认开启,如果使用时不注意很容易编译不通过或者产生语义问题,实际中也较少使用到,所以在此不做过多说明。
调用方向
前缀运算符调用方向从右至左,后缀运算符从左至右。中缀运算符理论上来说两者都可,但考虑到使用习惯和需求,Scala做了一个规定:以:
结尾的运算符为从右至左调用,其他的运算符从左至右调用。
调用方向会有什么影响呢?对于从左至右调用的运算符来说,比如+
,以下表达式是等价的:
1 2 3 |
1 + 2 + 3 1.+(2) + 3 1.+(2).+(3) |
然而,对于+:
运算符,以下表达式是等价的:
1 2 3 |
a +: b +: c a +: c.+:(b) c.+:(b).+:(a) |
也就是说,从右至左调用的运算符会将右边的对象作为调用方法的对象,反之亦然。
优先级
运算符的优先级是由第一个字符的优先级决定的,键盘上可以输入,且不是语法要素的符号的优先级见下表:
优先级 | 运算符首字母 | 备注 |
---|---|---|
1 | ~ ,@ ,# ,? ,\ |
Java中不会出现的符号 |
2 | * ,/ ,% |
乘除法 |
3 | + ,- |
加减法 |
4 | : |
|
5 | < ,> |
移位运算符和比较运算符 |
6 | ! ,= |
等于和不等于 |
7 | & |
与运算符 |
8 | ^ |
异或运算符 |
9 | | |
或运算符 |
10 | $ ,_ ,英文字母 |
$ 和_ 是可以写入标识符的符号,和上面的符号有本质区别 |
另外,前缀运算符高于所有中缀运算符。以=
结尾而又不以=
开始的运算符会被当做赋值运算符而优先级低于以上所有运算符。
总结
Scala的运算符本质上就是方法,这和其他语言的思路有很大的不同。使用运算符重载可以使程序更加简洁,但是却更加难懂。不过,只要使用得当,就可以很好地控制危害,尽量利用其优势。错的不是语言,是使用者。虽然语言提供了这种特性,但程序员不可滥用。