一、定义一个变量
Scala在首次定义一个变量时,必须在变量名前面添加关键字“var”或者“val”。用“var”修饰的变量,可以重新赋予新的值,并且把原值抛弃,类似于Java的非final变量。在后续重新赋值时,就不用再写“var”了。而用“val”修饰的变量,则禁止被重新赋值,类似于Java的final变量,换句话说,就是只能读不能写的变量。
变量名可以是任意的字母、数字和下划线的组合,但是不能以数字开头。Scala推荐的命名方法是“驼峰命名法”,即每个单词的首字母大写,并且变量名和函数名以小写字母开头,类、对象和特质则以大写字母开头。例如,“val isOne”,“class MyClass”。在首次定义变量时,就必须赋予具体的值来初始化。不能出现如下形式:
val x
x = 1
以下代码都是合法的变量定义:
scala> val x = 1
x: Int = 1scala> var y = 2
y: Int = 2scala> val msg = "Hello, world!"
msg: String = Hello, world!
var类型的变量在重新赋值时,新值必须和旧值是同一个类型,否则会发生类型匹配错误:
scala> var x = 1
x: Int = 1scala> x = 10
x: Int = 10scala> x = "abc"
<console>:12: error: type mismatch;
found : String("abc")
required: Int
x = "abc"
^
val类型的变量则直接禁止重新赋值:
scala> val x = 1
x: Int = 1scala> x = 10
<console>:12: error: reassignment to val
x = 10
^
如果要赋给多个变量相同的值,那么没必要逐条定义,而是在一条语句里用逗号间隔的变量名。例如:
scala> val a, b, c = 1
a: Int = 1
b: Int = 1
c: Int = 1
Scala的变量定义具有覆盖性,也就是说,如果出现了同名的变量,则后出现的变量会覆盖前面的变量。例如:
scala> val x = 1
x: Int = 1scala> val x = 10
x: Int = 10scala> x
res0: Int = 10
要注意的是,赋给变量的对象存在可变与不可变之分。要理解到底是变量指向的对象本身发生了改变,还是变量指向了新的对象。即使是val类型的变量,也能被赋予一个可变对象。这个可变对象能够被重新修改,例如,给可变映射添加新的键值对。事实上,这只是旧对象发生了改变,并未产生新的对象。
Scala提倡定义val类型的变量,因为它是函数式编程,而函数式编程的思想之一就是传入函数的参数不应该被改变。所以,在Scala里,所有函数的参数都必须是val类型的。但是,Scala也允许指令式编程,因而预留了var类型的变量,尽管并不推荐使用。对于习惯了指令式编程的读者,例如,喜欢编写“for(i = 0; i < N; i++)”来实现一个循环,很显然更倾向于使用var类型的变量,因为在这个for循环里,变量i被多次重新赋值。Scala推荐读者学会使用val,学会函数式编程。笔者也是学习C/C++出身的,但是现在已经完全习惯了函数式编程。使用val类型的一个好处就是,你不用去计算某个变量在某个时刻是什么值,因为val类型的变量一旦被初始化,就一直不变,直到被重新定义。
二、Scala的基本类型
Scala是静态语言,在编译期间会检查每个对象的类型。对于类型不匹配的非法操作,在编译时就能被发现。对于动态语言而言,这种非法操作需要等到运行时才能被发现,此时可能造成严重错误。所以,静态语言相比诸如Python这样的动态语言在某些方面是有优势的。对于Chisel而言,我们就需要这种优势。因为Chisel需要编译成Verilog,我们不能产生非法的Verilog语句并且等到模块运行时才去发现它。
Scala标准库定义了一些基本类型,如下表所示。除了“String”类型是属于java.lang包之外,其余都在Scala的包里。
Byte | 8-bit有符号整数,补码表示,范围是 到 |
Short | 16-bit有符号整数,补码表示,范围是 到 |
Int | 32-bit有符号整数,补码表示,范围是 到 |
Long | 64-bit有符号整数,补码表示,范围是 到 |
Char | 16-bit字符,Unicode编码,范围是 0 到 |
String | 字符串 |
Float | 32-bit单精度浮点数,符合IEEE 754标准 |
Double | 64-bit双精度浮点数,符合IEEE 754标准 |
Boolean | 布尔值,其值为true或者false |
事实上,在定义变量时,应该指明变量的类型,只不过Scala的编译器具有自动推断类型的功能,可以根据赋给变量的对象的类型,来自动推断出变量的类型。如果要显式声明变量的类型,或者无法推断时,则只需在变量名后面加上一个冒号“ : ”,然后在等号与冒号之间写出类型名即可。例如:
scala> val x: Int = 123
x: Int = 123scala> val y: String = "123"
y: String = 123scala> val z: Double = 1.2
z: Double = 1.2
Ⅰ、整数字面量
整数有四种类型,默认情况下推断为Int类型。如果字面量的结尾有l或者L,则推断为Long类型。此外,Byte和Short则需要定义变量时显式声明。注意,赋给的字面值不能超过类型的表示范围。
整数字面量默认是十进制的,但如果以“0x”或者“0X”开头,则字面量被认为是十六进制。十六进制的字母不区分大小写。例如:
scala> val a = 100
a: Int = 100scala> val b = 0X123Abc
b: Int = 1194684scala> val c: Byte = 200
<console>:11: error: type mismatch;
found : Int(200)
required: Byte
val c: Byte = 200
^scala> val d = 200L
d: Long = 200
Ⅱ、浮点数字面量
浮点数的字面量都是十进制的,类型默认是Double类型。可以增加一个字母“e”或者“E”,再添加一个整数作为指数,这样就构成10的n次幂。最末尾可以写一个“f”或者“F”,表示Float类型;也可以写一个“d”或者“D”,表示Double类型。注意,Double类型的字面量不能赋给Float类型的变量。虽然Float允许扩展成Double类型,但是会发生精度损失。例如:
scala> val a = 1.2E3
a: Double = 1200.0scala> val b = -3.2f
b: Float = -3.2scala> val c: Float = -3.2
<console>:11: error: type mismatch;
found : Double(-3.2)
required: Float
val c: Float = -3.2
^scala> val d: Double = -3.2F
d: Double = -3.200000047683716
Ⅲ、字符与字符串字面量
字符字面量是以单引号' '包起来的一个字符,采用Unicode编码。也可以用'\u编码号'的方式来构造一个字符,而且Unicode编码可以出现在代码的任何地方,甚至是名称命名。此外,还支持转义字符。例如:
scala> val a = 'A'
a: Char = Ascala> val b = '\u0041'
b: Char = Ascala> val c = '\u0042'
c: Char = Bscala> val \u0041\u0042 = 1
AB: Int = 1scala> val d = '\\'
d: Char = \
字符串就是用双引号" "包起来的字符序列,长度任意,允许掺杂转义字符。此外,也可以用前后各三个双引号""" """包起来,这样字符串里也能出现双引号,而且转义字符不会被解读。例如:
scala> val a = "\\\\\\"
a: String = \\\scala> val b = """So long \u0041 String \\\'\"!"""
b: String = So long A String \\\'\"!
Ⅳ、字符串插值
Scala包括了一个灵活的机制来支持字符串插值,这使得表达式可以被嵌入在字符串字面量中并被求值。第一种形式是s插值器,即在字符串的双引号前加一个s,形如s“…${表达式}…”。s插值器会对内嵌的每个表达式求值,对求值结果调用内置的toString方法,替换掉字面量中的表达式。从美元符号开始到首个非标识符字符(字母、数字、下划线和操作符的组合称为标识符,以及反引号对` `包起来的字符串)的部分会被当作表达式,如果有非标识符字符,就必须放在花括号里,且左花括号要紧跟美元符号。第二种形式是raw插值器,它与s插值器类似,只不过不识别转义字符。第三种形式是f插值器,允许给内嵌的表达式加上printf风格的指令,指令放在表达式之后并以百分号开始。指令语法来自java.util.Formatter。如:
scala> val name = "ABC"
name: String = ABCscala> println(s"$name DEFG")
ABC DEFGscala> s"Sum = ${1 + 10}"
res0: String = Sum = 11scala> s"\\\\"
res1: String = \\scala> raw"\\\\"
res2: String = \\\\scala> printf(f"${math.Pi}%.5f")
3.14159
三、总结
本章介绍了Scala定义变量的方法及基本变量类型,重点在于学会使用val类型的变量。