Scala笔记(一) —— 基本语法和函数

369 阅读13分钟

1 变量和数据类型

1.1 数据类型

  1. Scala中的一切数据都是对象,都是Any的子类
  2. Scala中的数据类型分为两大类:数值类型(AnyVal)、引用类型(AnyRef)
  3. Scala的数据类型仍然遵守低精度值类型向高精度值类型转换时自动转换(隐式转换)
  4. Scala中的StringOps是对java中的String增强
  5. Unit对应java中的void,用于方法返回值的位置,表示该方法内有返回值.Unit是一个数据类型,只有一个对象就是()
  6. Null是一个类型,只有一个对象就是null,是所有引用类型(AnyRef)的子类
  7. Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,这样可以把抛出的返回值返回给任何变量或函数

Scala数据类型.png

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

  1. 布尔类型也叫 Boolean 类型,Booolean 类型数据只允许取值 true 和 false
  2. 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 只有一个实例值,写成()。
Nullnull , Null 类型只有一个实例值 null
NothingNothing 类型在 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 数值类型自动转换

  1. 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
  2. 把精度大的数值类型赋值给精度小的数值类型时就会报错,反之就会进行自动类型转换
  3. byte,short 和 char 之间不会相互自动转换。
  4. 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 变量和常量

  1. 基本语法
var 变量名:变量类型  var i:Int =10
val 常量名:常量类型  val a:Int = 10 
  1. 案例
  • 声明变量时,类型可以省略,编译器自动推导,即类型推导
  • 类型确定后,就不能修改,说明 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. 字符串模板(插值字符串):通过$获取变量值
  1. 代码
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 键盘输入

  1. 基本语法
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
  1. 代码
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 算术运算符

运算符运算范例结果
+正号+33
-负号b=4,-b-4
+5+38
-5-32
*5*315
/5/51
%取模(取余)7%52
+字符串相加"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==3false
!=不等于4!=3true
<小于4<3false
大于4>3true
<=小于等于4<=3false
>=大于等于4>=3true
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
||逻辑或"(AB) 运算结果为 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 赋值运算符

  1. 基本语法
运算符描述示例
=简单的赋值运算符,将一个表达式的值赋给一个左值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中其实是没有运算符的,所有运算符都是方法

  1. 当调用对象的方法时,点.可以省略
  2. 当函数的参数只有一个或者没有参数,()可以省略
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 单分支

  1. 基本语法
if(条件表达式){
    执行代码块
}
  1. 案例
//输入人的年龄,如果该同志的年龄小于 18 岁,则输出“童年”
object TestIfElse {
    def main(args: Array[String]): Unit = {
        println("input age:")
        var age = StdIn.readShort()
        if (age < 18){
            println("童年")
        }
    }
}

3.1.2 双分支

  1. 基本语法
if(条件表达式){
    执行代码块1
}else{
    执行代码块2
}
  1. 案例
//输入年龄,如果年龄小于 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 多分枝

  1. 基本语法
if (条件表达式 1) {
    执行代码块 1
}
else if (条件表达式 2) {
    执行代码块 2
}
……
else {
    执行代码块 n
}
  1. 案例
//需求:输入年龄,如果年龄小于 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 嵌套分支

在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层。分支外面的分支结构称为外层分支。

  1. 基本语法
if(){
    if(){
    }else{
    }
}
  1. 案例
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 范围数据循环

  1. 基本语法
for(i<- 1 to 3){
    print(i+" ")
}
a. i表示循环的变量, <- 规定 

b. i将会从1-3循环,前后闭合
  1. 案例
object TestFor {
    def main(args: Array[String]): Unit = {
        for(i <- 1 to 5){
            println("宋宋,告别海狗人参丸吧"+i)
        }
    }
}

3.3.2 范围数据循环

  1. 基本语法
for(i <- 1 until 3) {
    print(i + " ")
}
(1)这种方式和前面的区别在于 i 是从 13-12)即使前闭合后开的范围

2.案例

object TestFor {
    def main(args: Array[String]): Unit = {
        for(i <- 1 until 5 + 1){
            println("宋宋,告别海狗人参丸吧" + i)
        }
    }
}

3.3.3 循环守卫

  1. 基本语法
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. 案例
//输出 15 中,不等于 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. 案例
//需求:输出 110 以内的所有奇数
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 引入变量

  1. 基本语法
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 循环返回值

  1. 基本语法
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 倒叙打印

  1. 如果想倒序打印一组数据,可以用 reverse。
for(i <- 1 to 10 reverse){
    println(i)
}

3.4 while和do while循环

While 和 do..While 的使用和 Java 语言中用法相同。

3.4.1 while循环

  1. 基本语法
循环变量初始化
while (循环条件) {
    循环体(语句)
    循环变量迭代
}

(1)循环条件是返回一个布尔值的表达式 (2)while 循环是先判断再执行语句 (3)与 for 语句不同,while 语句没有返回值,即整个 while 语句的结果是 Unit 类型() (4)因为 while 中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免 的使用变量,而变量需要声明在 while 循环的外部,那么就等同于循环的内部对外部的变量 造成了影响,所以不推荐使用,而是推荐使用 for 循环。

  1. 案例
object TestWhile {
    def main(args: Array[String]): Unit = {
        var i = 0
        while (i < 10) {
            println("宋宋,喜欢海狗人参丸" + i)
            i += 1
        }
    }
}

3.4.2 do while 循环

  1. 基本语法
循环变量初始化;
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 循环中断

  1. 基本说明
Scala中使用breakable控制结构来实现 breakcontinue 功能。
  1. 案例 需求 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. 基本说明 (1)将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for,while,do…while 均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过 3 层】
    (2)设外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要执行 m*n 次。

  2. 案例

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 基本语法

  1. 基本语法
def sum(x:Int,y:Int):Int={
    x+y
}

image.png

  1. 案例
    需求:定义一个函数,实现将传入的名称打印出来。
object Test1 {
  def main(args: Array[String]): Unit = {
    // 1. 定义函数
    def f(arg:String): Unit ={
      println(arg)
    }
    // 2. 函数调用
    f("hello world")
  }
}

4.1.2 函数和方法的区别

  1. 核心概念

    (1)为完成某一功能的程序语句的集合,称为函数。
    (2)类中的函数称之方法。

  2. 案例

    (1)Scala 语言可以在任何的语法结构中声明任何的语法
    (2)函数没有重载和重写的概念;方法可以进行重载和重写
    (3)Scala 中函数可以嵌套定义

image.png

4.1.3 函数的定义

  1. 定义
1)函数 1:无参,无返回值  
(2)函数 2:无参,有返回值
(3)函数 3:有参,无返回值
(4)函数 4:有参,有返回值
(5)函数 5:多参,无返回值
(6)函数 6:多参,有返回值 
  1. 案例
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. 至简原则细节
1return 可以省略,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 高阶函数

  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
  }

}
  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 _))
  }
}
  1. 函数可以作为函数返回值返回
object Test5 {
    def main(args: Array[String]): Unit = {
        def f1()={
            def f2()={
            }
            f2 _
        }
        val f=f1()
        // 因为 f1 函数的返回值依然为函数,所以可以变量 f 可以作为函数继续调用
        f()
        // 上面的代码可以简化为
        f1()()
    }
}

4.2.2 匿名函数

  1. 说明

    没有名字的函数就是匿名函数

    (x:Int)=>{函数体}

    x: 表示输入参数;Int:表示输入的参数类型;函数体:表示具体代码逻辑

  2. 案例

//需求 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 函数柯里化&闭包

  1. 说明

    闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包

    函数柯里化:把一个参数列表的多个参数,变成多个参数列表。

  2. 案例

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 递归

  1. 说明 一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用

  2. 案例

//递归
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 惰性加载

  1. 说明

    当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数

  2. 案例

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
  }
}