第一篇https://blog.csdn.net/m0_74021449/article/details/144887921
2.2 Scala的变量及函数
2.2.1变量定义与基本类型
变量声明
变量首次定义必须使用关键字var
或者val
,二者的区别是val修饰的变量禁止被重新赋值,它是一个只读的变量。首次定义变量时必须赋值进行初始化。var类型的变量重新赋值时新旧必须是同一个类型,而val类型变量无法被重新赋值。变量定义具有覆盖性,后声明的变量会覆盖前面的变量。
Scala推荐使用val
定义变量,函数式编程的思想之一就是传入函数的参数不应该改变。需要说明的是,使用val
定义的变量并不是不可变,而是这个指向关系不可变。当这个变量被声明,变量指向的对象就是唯一确定的,但这个对象本身可以改变,只是变量指向的位置不可变。
基本类型
Scala作为一种静态语言,在编译时会检查每个对象的类型,类型不匹配的非法操作会报错。Scala定义了一些标准类型:
类型 | 说明 |
---|---|
Byte | 8bit有符号整数,补码表示,范围是-27~27-1 |
Short | 16bit有符号整数,补码表示,范围是-215~215-1 |
Int | 32bit有符号整数,补码表示,范围是-231~231-1 |
Long | 64bit有符号整数,补码表示,范围是-263~263-1 |
Char | 16bit无符号字符,Unicode编码表示,范围是0~216-1 |
String | 字符串类型,属于java.bang包 |
Float | 32bit单精度浮点数 |
Double | 64bit双精度浮点数 |
Boolean | 布尔值,true or false |
定义变量时可以指定类型,也可以让编译器自动推断。显式指定类型的示例:
Scala
scala> val x: Int = 123
val x: Int = 123
scala> val y: Long = 123
val y: Long = 123
整数字面量
如果单独出现数字,没有任何说明,默认推断为Int类型;结尾有l或L的推断为Long类型,以0x开头的认为是十六进制,不区分大小写。Byte和Short类型需要显示指定:
Scala
scala> val x = 100
val x: Int = 100
scala> val y = 100L
val y: Long = 100
scala> val z: Byte = 200
-- [E007] Type Mismatch Error: -----------------------------------------------------------------------------------------
1 |val z: Byte= 200
| ^^^
| Found: (200 : Int)
| Required: Byte
|
| longer expla
Byte类型最大不超过127,超过限制的赋值将报错
浮点数字面量
浮点数字面量都是十进制的,默认是Double类型,En或en表示科学计数法10的n次方,末尾加一个f或F表示Float,D或d表示Double。Double字面量不能赋值给Float类型变量。
Scala
scala> val x: Float = -3.2
val x: Float = -3.2
scala> val x = -3.2
val x: Double = -3.2
scala> val x: Float = 3.2D
-- [E007] Type Mismatch Error: -----------------------------------------------------------------------------------------
1 |val x: Float = 3.2D
| ^^^^
| Found: (3.2d : Double)
| Required: Float
|
| longer explanation available when compiling with `-explain`
1 error found
字符字面量和字符串字面量
以单引号括住的一个字符表示一个字符字面量,采用Unicode编码,也可以使用'\u编号'来构建一个字符,本书说明Unicode编码可以出现在名称命名处,但在最新版本Scala编译器中会报错:
Scala
scala> val a = 'a'
val a: Char = a
scala> val \u0041 = 'a'
-- [E032] Syntax Error: ------------------------------------------------------------------------------------------------
1 |val \u0041 = 'a'
| ^
| pattern expected
|
| longer explanation available when compiling with `-explain`
字符串字面量是用双引号""括住的字符序列,长度是任意的。字符和字符串都支持转义字符。
字符串插值
表达式可以被嵌入在字符串字面量中被求值,有三种实现方法,一种是s插值器,一种是raw插值器,一种是f插值器。
s插值器形如 s"......${表达式}......"
其中花括号可以不加,但只会识别美元符号到首个非标识字符(字母、数字、下划线和操作符的组合交标识符,以及字符串)对于非标识字符如果想要求值,必须使用花括号:
Scala
scala> val name = "Jia"
val name: String = Jia
scala> s"Name = $name"
val res3: String = Name = Jia
scala> s"Result = ${1+2}"
val res4: String = Result = 3
raw插值器和s插值器类似,区别是不识别转义字符。
f插值器使用方法更灵活,支持格式控制:
Scala
scala> printf(f"${math.Pi}%.5f")
3.14159~
2.2.2函数及其几种形式
函数的定义
不多说,直接看代码:
Scala
scala> def max(x : Int, y: Int): Int = {
| if(x > y)
| x
| else
| y
| }
def max(x: Int, y: Int): Int
说明:x,y是输入参数(形参)后面跟的是它们的类型;Int={}是函数体,表示返回是一个Int类型整数。
备注:左侧的|只是编译器自己生成的,用于多行的代码,并不是语法体系中的一部分。
-
分号推断:语句末尾的分号是可选的,编译器会自动推断分号。如果一行有多条语句,则必须用分号隔开。
-
函数的返回结果:return关键字是可选的,编译器自动为函数体中的最后一个表达式加上return,建议不要显示声明return。返回结果的类型也可以自动推断,也就是说上面的Int = {},Int也是可以省略的。Uint类型表示无返回值,它不是必须的,编译器也可以自动推断;但如果显式声明Uint,则即便有可以返回的值也不会返回任何值。
-
等号与函数体:函数体是花括号括起来的部分,里面有多条语句,可以自动推断分号,返回最后一个表达式。当函数的返回类型没有显示声明,等号可以省略,但此时返回类型会变成Uint,建议不要省略等号,并且用显示声明返回类型。
-
无参函数:无参函数可以写空括号作为参数列表,也可以不写;但如果没有空括号,调用的时候禁止写空括号。
方法
方法指定义在class,object,trait中的函数,成为成员函数或方法,这与其他面向对象特性的语言一致。
嵌套函数
函数体内部可以嵌套定义内部函数,但无法被外界访问。
函数字面量
函数式编程认为函数的地位和一个Int值,String值是一样的,因此函数也可以成为一个函数的参数或返回值,也可以把函数赋值给一个变量。函数字面量是一种匿名函数,它可以存储在变量中,成为函数参数,或者当作返回值返回,定义形式是:
{参数1:参数1类型,参数2:参数2类型...} => {函数体}
它可以更精简地使用,用下划线作为占位符替代参数,只保留函数体:
Scala
scala> val f = (_: Int) + (_: Int)
val f: (Int, Int) => Int = Lambda/0x00000003015d55a8@6816b0cb
scala> f(1,2)
val res5: Int = 3
用def定义的函数和函数字面量都可以以函数为参数,返回函数。
Scala
scala> val add = (x:Int) => {(y:Int) => x + y}
val add: Int => Int => Int = Lambda/0x00000003015d6348@20f1e26
scala> add(1)(10)
val res6: Int = 11
这里是函数的嵌套,第一个函数表示输入是x,类型是Int,函数体是(y:Int)=> x+y
这是一个函数字面量,它的输入是y,类型是Int,函数体是x+y,并且把这个表达式结果返回。如果调用add(1),则返回的是(y:Int)=>1+y
这个函数字面量。
Scala
scala> def aFunc(f:Int => Int) = f(1) + 1
def aFunc(f: Int => Int): Int
scala> aFunc(x=>x+1)
val res7: Int = 3
aFunc的参数f是一个函数,它的输入类型是Int,返回类型也是Int,调用时传入的参数是函数字面量x=>x+1,首先计算f(1)=2,再计算返回值是3。
部分应用函数
上面的函数字面量实现了函数作为一等值的功能,使用def定义的函数也具有相同的功能,只不过需要借助部分应用函数的形式来实现。部分应用函数的意思就是给出函数的一部分参数,使其可以赋值给变量或者当作函数参数进行传递。废话不多数,直接上代码:
Scala
scala> def sum(x:Int,y:Int,z:Int) : Int = {x + y + z}
def sum(x: Int, y: Int, z: Int): Int
scala> val a1 = sum(4,5,6)
val a1: Int = 15
scala> val a2 = sum(4,_:Int,6)
val a2: Int => Int = Lambda/0x000000030167d3a0@c3acda3
scala> a2(5)
val res12: Int = 15
scala> val a3 = sum _
1 warning found
-- Warning: ------------------------------------------------------------------------------------------------------------
1 |val a3 = sum _
| ^^^^^
| The syntax `<function> _` is no longer supported;
| you can simply leave out the trailing ` _`
val a3: (Int, Int, Int) => Int = Lambda/0x00000003016767e0@2c427287
scala> val a3 = sum
val a3: (Int, Int, Int) => Int = Lambda/0x0000000301676e10@68603829
scala> a3(4,5,6)
val res9: Int = 15
注意:书中的写法 sum _在最新版的编译器中不被支持,现在无需写下划线,只需要把函数名直接赋值给变量即可直接调用。
Scala
scala> def needSum(f:(Int,Int,Int) => Int) = f(1,2,3)
def needSum(f: (Int, Int, Int) => Int): Int
scala> needSum(sum)
val res13: Int = 6
这里我们已经逐渐能够感受到函数式编程的魅力,当理解这种调用方式后将会有很灵活的应用。
闭包
一个函数除了使用它的参数以外,还可以使用定义在函数以外的其他变量,其中函数的参数成为绑定变量,函数以外的变量称为自由变量,这样的函数称为闭包。函数捕获的自由变量是函数定义之前的自由变量,若后面出现新的同名自由变量将前面的自由变量将其覆盖,函数与其无关。但如果自由变量是用var创建的可变对象,那么闭包随之改变。
Scala
scala> var a1 = 1
var a1: Int = 1
scala> val a2 = 100
val a2: Int = 100
scala> val add1 = (x : Int) => x + a1
val add1: Int => Int = Lambda/0x000000030167fb08@23b456ac
scala> val add2 = (x : Int) => x + a2
val add2: Int => Int = Lambda/0x00000003016843f0@525b416f
scala> add1(1)
val res14: Int = 2
scala> add2(1)
val res15: Int = 101
scala> a1 = 100
a1: Int = 100
scala> add1(1)
val res16: Int = 101
scala> var a1 = 1
var a1: Int = 1
scala> add1(1)
val res17: Int = 101
scala> val a2 = 1
val a2: Int = 1
scala> add2(1)
val res18: Int = 101
阅读这段程序的对比已经很明显地了解到它的性质。在后面改变var变量的值,闭包随之改变,但对于var和val变量重新定义将前面的覆盖,闭包的值不会随之改变。
函数的特殊调用形式
-
具名参数:函数调用时传入的参数是按照先后顺序传递的,但如果显式声明参数的名字,可以无视参数顺序。按位置传递的参数和按名字传递的参数可以混用。
-
默认参数值:函数定义时可以给参数一个默认值,如果调用函数缺省了这个参数,会使用默认值。
-
重复参数:允许把函数的最后一个参数标记为重复参数,形式在最后一个参数的类型后面加上星号。
柯里化
对于大多数编程语言而言,函数只能有一个参数列表,但是列表中可以有若干用逗号间隔的参数。Scala的特性柯里化允许一个函数可以有任意个参数列表,它与另一个语法搭配使用,即当参数列表中只有一个参数时,调用该函数时允许单个参数不用圆括号括起来,改用花括号也可以。
Scala
scala> def add(x:Int)(y:Int)(z:Int) = x+y+z
def add(x: Int)(y: Int)(z: Int): Int
scala> add(1)
val res19: Int => Int => Int = Lambda/0x0000000301686b40@7ece1800
scala> add(1)(2)
val res20: Int => Int = Lambda/0x00000003016872f0@3b3e9814
scala> add(1)(2)(3)
val res21: Int = 6
scala> add(1)(2){3}
val res22: Int = 6
scala> add{1}(2)(3)
val res23: Int = 6
在这里我们看出来其实柯里化就是把它处理成了一个嵌套的函数。
传名参数
如果一个函数是无参函数,调用函数时,传递进去的函数字面量可以只写函数体。
Scala
//常规调用方法
scala> val a1 = 1
val a1: Int = 1
scala> val a2 = 2
val a2: Int = 2
scala> def add(f: () => Int) = {a1 + a2 + f()}
def add(f: () => Int): Int
scala> add(() => 1)
val res24: Int = 4
//传名参数调用方法
scala> def add2(f: => Int) = {a1 + a2 + f}
def add2(f: => Int): Int
scala> add2(1)
val res26: Int = 4
注:这里传入的f函数是一个没有输入,直接返回传入的值的函数。
这两种版本都是在使用到传入的函数时才会计算这个函数中的表达式,但如果改写成下面这样:(去掉=>),那么就变成先计算表达式再传入值。
Scala
scala> def add3(f:Boolean) = {
| if(f)
| a1 + a2
| else
| 0
| }
def add3(f: Boolean): Int
scala> add3(1<2)
val res27: Int = 3
scala> add3(1>2)
val res28: Int = 0
scala> add3(1/0)
-- [E007] Type Mismatch Error: -----------------------------------------------------------------------------------------
1 |add3(1/0)
| ^^^
| Found: Int
| Required: Boolean
|
| longer explanation available when compiling with `-explain`
1 error found