1 变量和数据类型
1.1 数据类型
- Scala中的一切数据都是对象,都是Any的子类
- Scala中的数据类型分为两大类:数值类型(AnyVal)、引用类型(AnyRef)
- Scala的数据类型仍然遵守低精度值类型向高精度值类型转换时自动转换(隐式转换)
- Scala中的StringOps是对java中的String增强
- Unit对应java中的void,用于方法返回值的位置,表示该方法内有返回值.Unit是一个数据类型,只有一个对象就是()
- Null是一个类型,只有一个对象就是null,是所有引用类型(AnyRef)的子类
- Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,这样可以把抛出的返回值返回给任何变量或函数
1.1.1 整数类型(Byte、Short、Int、Long)
| 数据类型 | 范围 |
|---|---|
| Byte[1] | 8 位有符号补码整数。数值区间为 -128 到 127 |
| Short[2] | 16 位有符号补码整数。数值区间为 -32768 到32767 |
| Int [4] | 32 位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
| Long [8] | 64 位有符号补码整数。数值区间为 -9223372036854775808 到9223372036854775807 = 2 的(64-1)次方-1 |
object TestDataType {
def main(args: Array[String]): Unit = {
//Scala 各整数类型有固定的表示范围和字段长度,不受具体操作的影响,以保证Scala 程序的可移植性
// 正确
var n1:Byte = 127
var n2:Byte = -128
// 错误
// var n3:Byte = 128
// var n4:Byte = -129
//Scala 的整型,默认为 Int 型,声明 Long 型,须后加‘l’或‘L
var n5 = 10
println(n5)
var n6 = 9223372036854775807L
println(n6)
}
}
1.1.2 浮点类型(Float、Double)
| 数据类型 | 范围 |
|---|---|
| Float[4] | 32 位, IEEE 754 标准的单精度浮点数 |
| Double[8] | 64 位 IEEE 754 标准的双精度浮点数 |
//Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加‘f’或‘F’
// 建议,在开发中需要高精度小数时,请选择 Double
var n7 = 2.2345678912f
var n8 = 2.2345678912
println("n7=" + n7)
println("n8=" + n8)
1.1.3 字符类型(Char)
object TestCharType {
def main(args: Array[String]): Unit = {
//(1)字符常量是用单引号 ' ' 括起来的单个字符。
var c1: Char = 'a'
println("c1=" + c1)
//注意:这里涉及自动类型提升,其实编译器可以自定判断是否超出范围,
//不过 idea 提示报错
var c2:Char = 'a' + 1
println(c2)
//(2)\t :一个制表位,实现对齐的功能
println("姓名\t 年龄")
//(3)\n :换行符
println("西门庆\n 潘金莲")
//(4)\\ :表示\
println("c:\\岛国\\avi")
//(5)\" :表示"
println("同学们都说:\"大海哥最帅\"")
}
}
1.1.4 尔类型:Boolean
- 布尔类型也叫 Boolean 类型,Booolean 类型数据只允许取值 true 和 false
- boolean 类型占 1 个字节。
object TestBooleanType {
def main(args: Array[String]): Unit = {
var isResult : Boolean = false
var isResult2 : Boolean = true
}
}
1.1.5 Unit 类型、Null 类型和 Nothing 类型
| 数据类型 | 描述 |
|---|---|
| Unit | 表示无值,和其他语言中 void 等同。用作不返回任何结果的方法的结果类型。Unit 只有一个实例值,写成()。 |
| Null | null , Null 类型只有一个实例值 null |
| Nothing | Nothing 类型在 Scala 的类层级最低端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,可以用 Nothing 来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性) |
def main(args: Array[String]): Unit = {
def sayOk : Unit = {// unit 表示没有返回值,即 void
}
println(sayOk)
//null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型
var cat = new Cat();
cat = null // 正确
var n1: Int = null // 错误
//Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于 Nothing 是其他任意类型的子类,他还能跟要求返回值的方法兼容
def test() : Nothing={
throw new Exception()
}
test
}
class Cat {
}
1.1.6 数值类型自动转换
- 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
- 把精度大的数值类型赋值给精度小的数值类型时就会报错,反之就会进行自动类型转换
- byte,short 和 char 之间不会相互自动转换。
- byte,short,char 他们三者可以计算,在计算时首先转换为 int 类型。
def main(args: Array[String]): Unit = {
//(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数值类型,然后再进行计算。
var n = 1 + 2.0
println(n) // n 就是 Double
//(2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
var n2 : Double= 1.0
//var n3 : Int = n2 //错误,原因不能把高精度的数据直接赋值和低精度。
//(3)(byte,short)和 char 之间不会相互自动转换。
var n4 : Byte = 1
//var c1 : Char = n4 //错误
var n5:Int = n4
//(4)byte,short,char 他们三者可以计算,在计算时首先转换为 int类型。
var n6 : Byte = 1
var c2 : Char = 1
// var n : Short = n6 + c2 //当 n6 + c2 结果类型就是 int
// var n7 : Short = 10 + 90 //错误
}
1.1.7 强制类型转换
自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出
def main(args: Array[String]): Unit = {
//(1)将数据由高精度转换为低精度,就需要使用到强制转换
var n1: Int = 2.5.toInt // 这个存在精度损失
//(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
var r1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt // 10 *3 + 6*1 = 36
var r2: Int = (10 * 3.5 + 6 * 1.5).toInt // 44.0.toInt = 44
println("r1=" + r1 + " r2=" + r2)
}
1.1.8 数值类型和 String 类型间转换
def main(args: Array[String]): Unit = {
//(1)基本类型转 String 类型(语法:将基本类型的值+"" 即可)
var str1 : String = true + ""
var str2 : String = 4.5 + ""
var str3 : String = 100 +""
//(2)String 类型转基本数值类型(语法:调用相关 API)
var s1 : String = "12"
var n1 : Byte = s1.toByte
var n2 : Short = s1.toShort
var n3 : Int = s1.toInt
var n4 : Long = s1.toLong
}
1.2 变量和常量
- 基本语法
var 变量名:变量类型 var i:Int =10
val 常量名:常量类型 val a:Int = 10
- 案例
- 声明变量时,类型可以省略,编译器自动推导,即类型推导
- 类型确定后,就不能修改,说明 Scala 是强数据类型语言
- 变量声明时,必须要有初始值
- 在声明/定义一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变,val 修饰的变量不可改
- var 修饰的对象引用可以改变,val 修饰的对象则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)
def main(args: Array[String]): Unit = {
//(1)声明变量时,类型可以省略,编译器自动推导,即类型推导
var age = 18
age = 30
//(2)类型确定后,就不能修改,说明 Scala 是强数据类型语言。
// age = "tom" // 错误
//(3)变量声明时,必须要有初始值
// var name //错误
//(4)在声明/定义一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变,val 修饰的变量不可改。
var num1 = 10 // 可变
val num2 = 20 // 不可变
num1 = 30 // 正确
//num2 = 100 //错误,因为 num2 是 val 修饰的
// p1 是 var 修饰的,p1 的属性可以变,而且 p1 本身也可以变
var p1 = new Person()
p1.name = "dalang"
p1 = null
// p2 是 val 修饰的,那么 p2 本身就不可变(即 p2 的内存地址不能变)但是,p2 的属性是可以变,因为属性并没有用 val 修饰。
val p2 = new Person()
p2.name="jinlian"
// p2 = null // 错误的,因为 p2 是 val 修饰的
}
class Person{
var name : String = "jinlian"
}
1.3 字符串输出
1.基本语法
1. 字符串,通过+号连接
2. printf 用法:字符串,通过%传值。
3. 字符串模板(插值字符串):通过$获取变量值
- 代码
def main(args: Array[String]): Unit = {
var name: String = "jinlian"
var age: Int = 18
//(1)字符串,通过+号连接
println(name + " " + age)
//(2)printf 用法字符串,通过%传值。
printf("name=%s age=%d\n", name, age)
//(3)字符串,通过$引用
//多行字符串,在 Scala 中,利用三个双引号包围多行字符串就可以实现。
//输入的内容,带有空格、\t 之类,导致每一行的开始位置不能整洁对齐。
//应用 scala 的 stripMargin 方法,在 scala 中 stripMargin 默认是“|”作为连接符,//在多行换行的行头前面加一个“|”符号即可。
val s =
"""
|select
| name,
| age
|from user
|where name="zhangsan"
""".stripMargin
println(s)
//如果需要对变量进行运算,那么可以加${}
val s1 =
s"""
|select
| name,
| age
|from user
|where name="$name" and age=${age+2}
""".stripMargin
println(s1)
val s2 = s"name=$name"
println(s2)
}
1.4 键盘输入
- 基本语法
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
- 代码
import scala.io.StdIn
object TestInput {
def main(args: Array[String]): Unit = {
// 1 输入姓名
println("input name:")
var name = StdIn.readLine()
// 2 输入年龄
println("input age:")
var age = StdIn.readShort()
// 3 输入薪水
println("input sal:")
var sal = StdIn.readDouble()
// 4 打印
println("name=" + name)
println("age=" + age)
println("sal=" + sal)
}
}
2. 运算符
2.1 算术运算符
| 运算符 | 运算 | 范例 | 结果 |
|---|---|---|---|
| + | 正号 | +3 | 3 |
| - | 负号 | b=4,-b | -4 |
| + | 加 | 5+3 | 8 |
| - | 减 | 5-3 | 2 |
| * | 乘 | 5*3 | 15 |
| / | 除 | 5/5 | 1 |
| % | 取模(取余) | 7%5 | 2 |
| + | 字符串相加 | "He"+"llo" | ”Hello |
对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整 数部分而舍弃小数部分
object TestArithmetic {
def main(args: Array[String]): Unit = {
//(1)对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。
val r1: Int = 10 / 3
println("r1= " + r1) // r1= 3
val r2: Double = 10 / 3
println("r2= " + r2) // r2= 3.0
val d: Double = 10 / 3.0
// 含义:保留小数点 2位,使用四舍五入
println("d= " + d.formatted("%.2f")) //d= 3.33
//(2)对一个数取模 a%b,和 Java 的取模规则一样。
val r4: Int = 10 % 3
println("r4= " + r4) // r4=1
}
}
2.2 关系运算符
| 运算符 | 运算 | 范例 | 结果 |
|---|---|---|---|
| == | 相等于 | 4==3 | false |
| != | 不等于 | 4!=3 | true |
| < | 小于 | 4<3 | false |
| 大于 | 4>3 | true | |
| <= | 小于等于 | 4<=3 | false |
| >= | 大于等于 | 4>=3 | true |
object TestRelation {
def main(args: Array[String]): Unit = {
// 测试:>、>=、<=、<、==、!=
val a = 2
val b = 1
println(a > b)
println(a >= b)
println(a <= b)
println(a < b)
println("a==b: " + (a == b))
println(a != b)
}
}
Java 和 Scala 中关于==的区别
Java: ==比较两个变量本身的值,即两个对象在内存中的首地址; equals 比较字符串中所包含的内容是否相同
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
输出结果:
false
true
Scala:==更加类似于 Java 中的 equals
def main(args: Array[String]): Unit = {
val s1 = "abc"
val s2 = new String("abc")
println(s1 == s2)
println(s1.eq(s2))
}
输出结果:
true
false
2.3 逻辑运算符
假定:变量 A 为 true,B 为 false
| 运算符 | 描述 | 实例 | ||
|---|---|---|---|---|
| && | 逻辑与 | (A && B) 运算结果为 false | ||
| || | 逻辑或 | "(A | B) 运算结果为 true" | |
| ! | 逻辑非 | !(A && B) 运算结果为 true |
def main(args: Array[String]): Unit = {
// 测试:&&、||、!
var a = true
var b = false
println("a&&b=" + (a && b)) // a&&b=false
println("a||b=" + (a || b)) // a||b=true
println("!(a&&b)=" + (!(a && b))) // !(a&&b)=true
}
2.4 赋值运算符
- 基本语法
| 运算符 | 描述 | 示例 |
|---|---|---|
| = | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值给 C |
| += | 相加后再赋值 | C += A 等于 C = C + A |
| -= | 相减后再赋值 | C -= A 等于 C = C - A |
| *= | 相乘后再赋值 | C *= A 等于 C = C * A |
| /= | 相除后再赋值 | C /= A 等于 C = C / A |
| %= | 求余后再赋值 | C %= A 等于 C = C % A |
| <<= | 左移后赋值 | C <<= 2 等于 C = C << 2 |
| >>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 |
| &= | 按位与后赋值 | C &= 2 等于 C = C & 2 |
| ^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 |
| |= | 按位或后赋值 | C |= 2 等于 C = C | 2 |
Scala 中没有++、--操作符,可以通过+=、-=来实现同样的效果
object TestAssignment {
def main(args: Array[String]): Unit = {
var r1 = 10
r1 += 1 // 没有++
r1 -= 2 // 没有--
}
}
2.5 位运算符
1.基本语法 下表中变量 a 为 60,b 为 13。
| 运算符 | 描述 | 实例 |
|---|---|---|
| & | 按位与运算符 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| | | 按位或运算符 | (a | b) 输出结果 61 ,二进制解释: 0011 1101 |
| 按位异或运算符 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 | |
| ~ | 按位取反运算符 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。 |
| << | 左移动运算符 | a << 2 输出结果 240 ,二进制解释: 0011 0000 |
| >> | 右移动运算符 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
| >>> | 无符号右移 | a >>>2 输出结果 15, 二进制解释: 0000 1111 |
object TestPosition {
def main(args: Array[String]): Unit = {
// 测试:1000 << 1 =>10000
var n1 :Int =8
n1 = n1 << 1
println(n1)
}
}
2.6 运算符的本质
在Scala中其实是没有运算符的,所有运算符都是方法
- 当调用对象的方法时,点.可以省略
- 当函数的参数只有一个或者没有参数,()可以省略
object TestOpt {
def main(args: Array[String]): Unit = {
// 标准的加法运算
val i:Int = 1.+(1)
// (1)当调用对象的方法时,.可以省略
val j:Int = 1 + (1)
// (2)如果函数参数只有一个,或者没有参数,()可以省略
val k:Int = 1 + 1
println(1.toString())
println(1 toString())
println(1 toString)
}
}
3. 流程控制
3.1 分支控制 if-else
3.1.1 单分支
- 基本语法
if(条件表达式){
执行代码块
}
- 案例
//输入人的年龄,如果该同志的年龄小于 18 岁,则输出“童年”
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age:")
var age = StdIn.readShort()
if (age < 18){
println("童年")
}
}
}
3.1.2 双分支
- 基本语法
if(条件表达式){
执行代码块1
}else{
执行代码块2
}
- 案例
//输入年龄,如果年龄小于 18 岁,则输出“童年”。否则,输出“成年”。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age:")
var age = StdIn.readShort()
if (age < 18){
println("童年")
}else{
println("成年")
}
}
}
3.1.3 多分枝
- 基本语法
if (条件表达式 1) {
执行代码块 1
}
else if (条件表达式 2) {
执行代码块 2
}
……
else {
执行代码块 n
}
- 案例
//需求:输入年龄,如果年龄小于 18 岁,则输出“童年”。如果年龄大于等于 18 且小于等于 30,则输出“中年”,否则,输出“老年”
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
if (age < 18){
println("童年")
}else if(age>=18 && age<30){
println("中年")
}else{
println("老年")
}
}
}
//Scala 中 if else 表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res :String = if (age < 18){
"童年"
}else if(age>=18 && age<30){
"中年"
}else{
"老年"
}
println(res)
}
}
// Scala 中返回值类型不一致,取它们共同的祖先类型。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18){
"童年"
}else if(age>=18 && age<30){
"中年"
}else{
100
}
println(res)
}
}
3.2 嵌套分支
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层。分支外面的分支结构称为外层分支。
- 基本语法
if(){
if(){
}else{
}
}
- 案例
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res :String = if (age < 18){
"童年"
}else {
if(age>=18 && age<30){
"中年"
}else{
"老年"
}
}
println(res)
}
}
3.3 For 循环控制
3.3.1 范围数据循环
- 基本语法
for(i<- 1 to 3){
print(i+" ")
}
a. i表示循环的变量, <- 规定
b. i将会从1-3循环,前后闭合
- 案例
object TestFor {
def main(args: Array[String]): Unit = {
for(i <- 1 to 5){
println("宋宋,告别海狗人参丸吧"+i)
}
}
}
3.3.2 范围数据循环
- 基本语法
for(i <- 1 until 3) {
print(i + " ")
}
(1)这种方式和前面的区别在于 i 是从 1 到 3-1
(2)即使前闭合后开的范围
2.案例
object TestFor {
def main(args: Array[String]): Unit = {
for(i <- 1 until 5 + 1){
println("宋宋,告别海狗人参丸吧" + i)
}
}
}
3.3.3 循环守卫
- 基本语法
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
(1)循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为 true 则进入循环体内部,为 false 则跳过,类似于continue。
(2)上面的代码等价
for (i <- 1 to 3){
if (i != 2) {
print(i + " ")
}
}
- 案例
//输出 1 到 5 中,不等于 3 的值
object TestFor {
def main(args: Array[String]): Unit = {
for (i <- 1 to 5 if i != 3) {
println(i + "宋宋")
}
}
}
3.3.4 循环步长
1.基本语法
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
//说明:by 表示步长
- 案例
//需求:输出 1 到 10 以内的所有奇数
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
3.3.5 嵌套循环
1.基本语法
for(i <- 1 to 3; j <- 1 to 3) {
println(" i =" + i + " j = " + j)
}
说明:没有关键字,所以范围后一定要加;来隔断逻辑
等价于
for (i <- 1 to 3) {
for (j <- 1 to 3) {
println("i =" + i + " j=" + j)
}
}
3.3.6 引入变量
- 基本语法
for(i <- 1 to 3; j = 4 - i) {
println("i=" + i + " j=" + j)
}
(1)for 推导式一行中有多个表达式时,所以要加 ; 来隔断逻辑
(2)for 推导式有一个不成文的约定:当 for 推导式仅包含单一表达式时使用圆括号, 当包含多个表达式时,一般每行一个表达式,并用花括号代替圆括号,如下
for {
i <- 1 to 3
j = 4 - i
} {
println("i=" + i + " j=" + j)
}
等价于
for (i <- 1 to 3) {
var j = 4 - i
println("i=" + i + " j=" + j)
}
3.3.7 循环返回值
- 基本语法
val res = for(i <- 1 to 10) yield i
println(res)
说明:将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字。开发中很少使用
2. 案例
//需求:将原数据中所有值乘以 2,并把数据返回到一个新的集合中。
object TestFor {
def main(args: Array[String]): Unit = {
var res = for(i <-1 to 10) yield {
i * 2
}
println(res) //Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
}
}
3.3.8 倒叙打印
- 如果想倒序打印一组数据,可以用 reverse。
for(i <- 1 to 10 reverse){
println(i)
}
3.4 while和do while循环
While 和 do..While 的使用和 Java 语言中用法相同。
3.4.1 while循环
- 基本语法
循环变量初始化
while (循环条件) {
循环体(语句)
循环变量迭代
}
(1)循环条件是返回一个布尔值的表达式 (2)while 循环是先判断再执行语句 (3)与 for 语句不同,while 语句没有返回值,即整个 while 语句的结果是 Unit 类型() (4)因为 while 中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免 的使用变量,而变量需要声明在 while 循环的外部,那么就等同于循环的内部对外部的变量 造成了影响,所以不推荐使用,而是推荐使用 for 循环。
- 案例
object TestWhile {
def main(args: Array[String]): Unit = {
var i = 0
while (i < 10) {
println("宋宋,喜欢海狗人参丸" + i)
i += 1
}
}
}
3.4.2 do while 循环
- 基本语法
循环变量初始化;
do{
循环体(语句)
循环变量迭代
} while(循环条件)
(1)循环条件是返回一个布尔值的表达式 (2)do..while 循环是先执行,再判断
2. 案例
object TestWhile {
def main(args: Array[String]): Unit = {
var i = 0
do {
println("宋宋,喜欢海狗人参丸" + i)
i += 1
} while (i < 10)
}
}
3.5 循环中断
- 基本说明
Scala中使用breakable控制结构来实现 break 和 continue 功能。
- 案例 需求 1:采用异常的方式退出循环
//需求 1:采用异常的方式退出循环
def main(args: Array[String]): Unit = {
try {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) throw new RuntimeException
}
}catch {
case e =>
}
println("正常结束循环")
}
需求 2:采用 Scala 自带的函数,退出循环
import scala.util.control.Breaks
def main(args: Array[String]): Unit = {
Breaks.breakable(
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) Breaks.break()
}
)
println("正常结束循环")
}
需求 3:对 break 进行省略
import scala.util.control.Breaks._
object TestBreak {
def main(args: Array[String]): Unit = {
breakable {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) break
}
}
println("正常结束循环")
}
}
需求 4:循环遍历 10 以内的所有数据,跳过(continue)5
object TestBreak {
def main(args: Array[String]): Unit = {
for (i <- 1 to 10){
breakable {
if (i == 5) {
break()
}
println(i)
}
}
}
}
3.6 多重循环
-
基本说明 (1)将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for,while,do…while 均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过 3 层】
(2)设外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要执行 m*n 次。 -
案例
object TestWhile {
def main(args: Array[String]): Unit = {
for (i <- 1 to 9) {
for (j <- 1 to i) {
print(j + "*" + i + "=" + (i * j) + "\t")
}
println()
}
}
}
4. 函数式编程
4.1 函数基础
4.1.1 基本语法
- 基本语法
def sum(x:Int,y:Int):Int={
x+y
}
- 案例
需求:定义一个函数,实现将传入的名称打印出来。
object Test1 {
def main(args: Array[String]): Unit = {
// 1. 定义函数
def f(arg:String): Unit ={
println(arg)
}
// 2. 函数调用
f("hello world")
}
}
4.1.2 函数和方法的区别
-
核心概念
(1)为完成某一功能的程序语句的集合,称为函数。
(2)类中的函数称之方法。 -
案例
(1)Scala 语言可以在任何的语法结构中声明任何的语法
(2)函数没有重载和重写的概念;方法可以进行重载和重写
(3)Scala 中函数可以嵌套定义
4.1.3 函数的定义
- 定义
(1)函数 1:无参,无返回值
(2)函数 2:无参,有返回值
(3)函数 3:有参,无返回值
(4)函数 4:有参,有返回值
(5)函数 5:多参,无返回值
(6)函数 6:多参,有返回值
- 案例
object Test_FunctionDeclare {
def main(args: Array[String]): Unit = {
// 函数 1:无参,无返回值
def test1(): Unit = {
println("无参,无返回值")
}
test1()
// 函数 2:无参,有返回值
def test2(): String = {
return "无参,有返回值"
}
println(test2())
// 函数 3:有参,无返回值
def test3(s: String): Unit = {
println(s)
}
test3("jinlian")
// 函数 4:有参,有返回值
def test4(s: String): String = {
return s + "有参,有返回值"
}
println(test4("hello "))
// 函数 5:多参,无返回值
def test5(name: String, age: Int): Unit = {
println(s"$name, $age")
}
test5("dalang", 40)
}
}
4.1.4 函数参数
(1)可变参数
(2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
(3)参数默认值,一般将有默认值的参数放置在参数列表的后面
(4)带名参数
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)可变参数
def test( s : String* ): Unit = {
println(s)
}
// 有输入参数:输出 Array
test("Hello", "Scala")
// 无输入参数:输出 List()
test()
// (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
def test2( name : String, s: String* ): Unit = {
println(name + "," + s)
}
test2("jinlian", "dalang")
// (3)参数默认值
def test3( name : String, age : Int = 30 ): Unit = {
println(s"$name, $age")
}
// 如果参数传递了值,那么会覆盖默认值
test3("jinlian", 20)
// 如果参数有默认值,在调用的时候,可以省略这个参数
test3("dalang")
// 一般情况下,将有默认值的参数放置在参数列表的后面
def test4( sex : String = "男", name : String ): Unit = {
println(s"$name, $sex")
}
// Scala 函数中参数传递是,从左到右
//test4("wusong")
//(4)带名参数
test4(name="ximenqing")
}
}
4.1.4 函数至简原则
- 至简原则细节
(1)return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
(2)如果函数体只有一行代码,可以省略花括号
(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
(4)如果有 return,则不能省略返回值类型,必须指定
(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
(6)Scala 如果期望是无返回值类型,可以省略等号
(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
object Test2 {
def main(args: Array[String]): Unit = {
// 标准写法
def f(string: String): String = {
return string + "jinlian"
}
//至简原则:能省则省
//(1) return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
def f1(s: String): String = {
s + "jinlian"
}
// (2)如果函数体只有一行代码,可以省略花括号
def f2(s: String): String = s + "jinlian"
//(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
def f3(s: String) = s + "jinlian"
//(4)如果有 return,则不能省略返回值类型,必须指定。
def f4(): String = {
return "ddd"
}
println(f4())
//(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
def f5(): Unit = {
return "sss"
}
//(6)Scala 如果期望是无返回值类型,可以省略等号
// 将无返回值的函数称之为过程
def f6() {
"dalang6"
}
println(f6())
//(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def f7() = "dalang"
println(f7())
println(f7)
//(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f8 = "dalang2"
println(f8)
//(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def f9 = (x:String)=>{println("wusong")}
def f10(f:String=>Unit) = {
f("")
}
f10(f9)
println(f10((x:String)=>{println("wusong")}))
}
}
4.2 高级函数
4.2.1 高阶函数
- 函数可以作为值进行传递
object Test3 {
def main(args: Array[String]): Unit = {
//1. 调用foo函数,把值返回给变量f
val f = foo()
println(f)
//(2)在被调用函数 foo 后面加上 _,相当于把函数 foo 当成一个整体,传递给变量 f1
val f1 = foo _
println(f1)
//(3)如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给变量
var f2: () => Int = foo
}
def foo(): Int = {
println("foo ...")
1
}
}
- 函数可以作为参数传递
object Test4 {
def main(args: Array[String]): Unit = {
// (1)定义一个函数,函数参数还是一个函数签名;f 表示函数名称;(Int,Int)表示输入两个 Int 参数;Int 表示函数返回值
def f1(f: (Int, Int) => Int): Int = {
f(2, 4)
}
// (2)定义一个函数,参数和返回值类型和 f1 的输入参数一致
def add(a: Int, b: Int): Int = a + b
// (3)将 add 函数作为参数传递给 f1 函数,如果能够推断出来不是调用,_可以省略
println(f1(add))
println(f1(add _))
}
}
- 函数可以作为函数返回值返回
object Test5 {
def main(args: Array[String]): Unit = {
def f1()={
def f2()={
}
f2 _
}
val f=f1()
// 因为 f1 函数的返回值依然为函数,所以可以变量 f 可以作为函数继续调用
f()
// 上面的代码可以简化为
f1()()
}
}
4.2.2 匿名函数
-
说明
没有名字的函数就是匿名函数
(x:Int)=>{函数体}
x: 表示输入参数;Int:表示输入的参数类型;函数体:表示具体代码逻辑
-
案例
//需求 1:传递的函数有一个参数
object Test6 {
def main(args: Array[String]): Unit = {
//1. 定义一个函数:参数包含数据和逻辑函数
def operation(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
//2. 定义逻辑函数
def op(ele: Int): Int = {
ele + 1
}
//3. 标准的函数调用
val arr: Array[Int] = operation(Array(1, 2, 3, 4), op)
println(arr.mkString(","))
//4. 采用匿名函数
val arr1: Array[Int] = operation(Array(1, 2, 3, 4), (ele: Int) => {
ele + 1
})
println(arr1.mkString(","))
//(4.1)参数的类型可以省略,会根据形参进行自动的推导
val arr2: Array[Int] = operation(Array(1, 2, 3, 4), (ele) => {
ele + 1
})
println(arr2.mkString(","))
//(4.2) 类型省略之后返现只有一个参数,则圆括号可以省略,其他情况:没有参数和参数超过 1 的永远不能省略圆括号
val arr3: Array[Int] = operation(Array(1, 2, 3, 4), ele => {
ele + 1
})
println(arr3.mkString(","))
// (4.3) 匿名函数如果只有一行,则大括号也可以省略
val arr4 = operation(Array(1, 2, 3, 4), ele => ele + 1)
println(arr4.mkString(","))
//(4.4)如果参数只出现一次,则参数省略且后面参数可以用_代替
val arr5: Array[Int] = operation(Array(1, 2, 3, 4), _ + 1)
println(arr5.mkString(","))
}
}
需求 2:传递的函数有两个参数
object Test7 {
def main(args: Array[String]): Unit = {
def calculator(a: Int, b: Int, op: (Int, Int) => Int): Int = {
op(a, b)
}
//1. 标准版
println(calculator(2, 3, (x: Int, y: Int) => {
x + y
}))
//2. 如果只有一行,则大括号可以省略
println(calculator(2, 3, (x: Int, y: Int) => x + y))
//3. 如果参数类型可以省略,会根据形参进行自动的推导
println(calculator(2, 3, (x, y) => x + y))
//4. 如果参数只出现一次,则参数类型省略且后面参数可以用_代替
println(calculator(2, 3, _ + _))
}
}
4.2.3 高阶函数案例
需求:模拟 Map 映射、Filter 过滤、Reduce 聚合
object Test8 {
def main(args: Array[String]): Unit = {
//map映射
def map(array: Array[Int], op: Int => Int) = {
for (elem <- array) yield op(elem)
}
var arr = map(Array(1, 2, 3, 4), (x: Int) => {
x * x
})
println(arr.mkString(","))
//filter 过滤
def filter(array: Array[Int], op: Int => Boolean) = {
val arr1: ArrayBuffer[Int] = ArrayBuffer[Int]()
for (elem <- arr if op(elem)) {
arr1.append(elem)
}
arr1.toArray
}
val arr1: Array[Int] = filter(Array(1, 2, 3, 4), _ % 2 == 1)
println(arr1.mkString(","))
//reduce 聚合
def reduce(arr: Array[Int], op: (Int, Int) => Int) = {
var init: Int = arr(0)
for (elem <- 1 until arr.length) {
init = op(init, elem)
}
init
}
val arr2: Int = reduce(Array(1, 2, 3, 4), _ * _)
println(arr2)
}
}
4.2.4 函数柯里化&闭包
-
说明
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
-
案例
object Test9 {
def main(args: Array[String]): Unit = {
def f1() = {
var a: Int = 10
def f2(b: Int) = {
a + b
}
f2 _
}
//在调用时,f1函数执行完毕,局部变量a应该随着栈空间释放掉
val f = f1()
//但是在此处变量a其实并没有释放而是包含在了f2函数的内部,形成了闭合的效果
println(f(3))
println(f1()(3))
//函数柯里化,就是将复杂的参数逻辑变得简单化,函数柯里化一定存在闭包
def f3()(b: Int) = {
var a = 10
a + b
}
println(f3()(3))
}
}
4.2.5 递归
-
说明 一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
-
案例
//递归
object Test_Recursion {
def main(args: Array[String]): Unit = {
println(fact(5))
println(tailFact(5))
}
def fact(i: Int): Int = {
if (i == 0) return 1
fact(i - 1) * i
}
//尾递归优化
def tailFact(n: Int): Int = {
@tailrec
def loop(n: Int, currRes: Int): Int = {
if (n == 0) return currRes
loop(n - 1, currRes * n)
}
loop(n, 1)
}
}
4.2.6 控制抽象
object Test_ControlAbstraction {
def main(args: Array[String]): Unit = {
//传值参数
def f0(a: Int) = {
println("a: " + a)
println("a: " + a)
}
f0(23)
def f1(): Int = {
println("f1调用")
12
}
f0(f1())
//传名参数,传递的不再是具体的值,而是代码块
def f2(a: => Int) = {
println("a: " + a)
println("a: " + a)
}
println("==============")
f2(f1())
}
}
注意:Java 只有值调用;Scala 既有值调用,又有名调用。
4.2.7 惰性加载
-
说明
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数
-
案例
object Test_Lazy {
def main(args: Array[String]): Unit = {
lazy val result: Int = sum(13, 47)
println("1 函数调用")
println("2 result= " + result)
println("4 result= " + result)
}
def sum(i: Int, i1: Int): Int = {
println("3 sum调用")
i + i1
}
}