Scala之函数Day-2

Scala

函数(Function)

概述

  1. 将一段逻辑进行封装便于进行重复使用,被封装的这段逻辑就是函数。在Scala中,必须通过def来定义函数

  2. 基本语法

    scala 复制代码
    def 函数名(参数列表) : 返回值类型 = {
        函数体
        return 返回值
    }
  3. 案例

    scala 复制代码
    // 案例:定义函数计算两个整数的和
    // 标准写法
    def sum(a: Int, b: Int): Int = {
        return a + b
    }
    // 因为在Scala中,所有的结构都必须有返回值
    // 所以在函数中,如果不指定,默认会将最后一行的计算结果作为返回值
    // 也因此可以省略return不写
    def sum(a: Int, b: Int): Int = {
        a + b
    }
    // 如果函数体只有一句话,那么此时也可以省略{}不写
    def sum(a: Int, b: Int): Int = a + b
    // 参数类型非常明确,并且可以由计算结果来确定返回值类型
    // 那么也就意味着此时可以推导出结果类型,因此可以省略返回值类型不写
    def sum(a: Int, b: Int) = a + b
  4. 练习:定义一个函数,判断一个数字是否是质数

    scala 复制代码
    package com.fesco.method
    
    object MethodExer1 {
    
      def main(args: Array[String]): Unit = {
    
        println(isPrime(19))
        println(isPrime(25))
      }
    
      // 判断数字是否是质数
      /*
        定义函数,明确问题:
        1. 是否需要参数 - 是否有未知量来参与运算,需要在调用函数的时候传入这个数据
        2. 明确结果类型 - 判断是不是的问题,那么结果类型应该是布尔值
        3. 明确计算逻辑
       */
      def isPrime(n: Int): Boolean = {
        // 判断传入的参数n是否是一个质数
        // 1及以下的数字,不是质数
        if (n <= 1) false
        else if (n == 2) true
        else {
          for (i <- 2 until n) {
            if (n % i == 0) return false
          }
          // 如果整个循环结束,都没有返回false,那么说明所有数字都无法整除
          true
        }
      }
    
    }
  5. 案例

    scala 复制代码
    // 打印n行*组成的三角形
    def printStar(n: Int): Unit = {
        for (i <- 1 to n)
          println("*" * i)
    }
    // 可以推导出结果类型
    def printStar(n: Int) = {
        for (i <- 1 to n)
          println("*" * i)
    }
    // 如果返回值类型是Unit,那么此时可以省略=不写
    def printStar(n: Int) {
        for (i <- 1 to n)
          println("*" * i)
    }
    // 如果代码只有一行,并且返回值类型还是Unit,那么此时=或者{}只能省略其一
    def printStar(n: Int) =  for (i <- 1 to n) println("*" * i)
  6. 案例

    scala 复制代码
    package com.fesco.method
    
    object MethodDemo3 {
    
      def main(args: Array[String]): Unit = {
    
        // 如果函数在调用的时候没有参数,()可以写可以不写
        println(rand100())
        println(rand100)
        // 如果函数在定义的时候就没有(),那么调用的时候也不能写()
        println(rand)
    
      }
    
      // 产生1-100之间的随机数
      def rand100():Int = (Math.random() * 100 + 1).toInt
      // 函数没有参数的,因此()可以省略
      def rand:Int = (Math.random() * 100 + 1).toInt
    
    }

参数

可变参数
  1. 所谓可变参数,指的是在调用函数的时候,参数个数可以变化

  2. 案例

    scala 复制代码
    package com.fesco.method
    
    object MethodDemo4 {
    
      def main(args: Array[String]): Unit = {
    
        println(sum(4.2, 1.84, 8.741, 7.2, 2.05))
    
      }
    
      // 计算传入的数字的和
      // 通过*来定义可变参数
      def sum(nums: Double*): Double = {
        var sum = 0.0
        for (n <- nums)
          sum += n
        // 将结果返回
        sum
      }
    
    }
  3. 注意:函数中最多只能定义一个可变参数,并且必须放在参数列表的末尾

默认参数
  1. 默认参数,在定义函数的时候,就给参数一个默认值。在调用函数的时候,如果指定了值,就使用指定的来计算;如果没有指定,就使用默认值来计算

  2. 案例

    scala 复制代码
    package com.fesco.method
    
    object MethodDemo5 {
    
      def main(args: Array[String]): Unit = {
    
        // 在调用函数的时候,如果没有传入折扣,那么就使用默认值
        println(offPrice(180))
        // 如果传入了折扣,那么就按照传入的折扣来计算
        println(offPrice(150, 0.88))
    
      }
    
      // 案例:计算打折之后的价格
      // 如果需要打折,那么就指定折扣
      // 如果不需要打折,希望off就是1
      def offPrice(price: Double, off: Double = 1.0): Double = {
        if (off <= 0 || off > 1) throw new IllegalArgumentException
        price * off
      }
    }

函数的调用

  1. Scala中,也是通过函数名(参数)的形式来调用函数,但是Scala提供了省略调用和带名调用

  2. 省略调用:如果函数没有参数,那么在调用函数的时候可以省略()不写

    scala 复制代码
    println()
    // 省略调用
    println
  3. 如果函数定义的时候有(),那么在调用的时候可以写()也可以不写();如果函数定义的时候就没有(),那么调用的时候就不能有()

  4. 带名调用:在调用函数的时候,指定参数名来赋值

    scala 复制代码
    package com.fesco.method
    
    object MethodDemo6 {
    
      def main(args: Array[String]): Unit = {
    
        info("bob", "男", 19)
        info(name = "bob", gender = "男", age = 19)
        // 带名调用的时候,参数顺序可以不一致
        info(gender = "男", age = 19, name = "bob")
    
        message(name = "David", age = 15)
    
        println(offPrice(180, off = 0.92))
    
      }
    
      def info(name: String, gender: String, age: Int) = println(s"姓名:$name\n性别:$gender\n年龄:$age")
    
      // 默认参数
      def message(name: String, gender: String = "男", age: Int) = println(s"姓名:$name\n性别:$gender\n年龄:$age")
    
      def offPrice(price: Double, vip: Int = 0, off: Double = 1) = {
        if (vip <= 0) price * off
        else if (vip <= 3) price * 0.95 * off
        else if (vip <= 7) price * 0.9 * off
        else price * 0.85 * off
      }
    
    }

函数和方法

  1. Java中,函数就是方法,方法也是函数。在Scala中,函数的范围会比方法要稍微大一点

  2. Scala中,函数可以定义在任何位置,即函数可以定义在类或者函数内

    scala 复制代码
    object MethodDemo7 {
    
      def main(args: Array[String]): Unit = {
    
        // 在函数中定义函数
        def sum(x: Int, y: Int) = x + y
    
        println(sum(3, 5))
    
      }
    
    }
  3. 如果需要细分:定义在类中的函数称之为方法,定义在其他地方的就是函数

  4. 在Scala中,函数是"一等公民",即函数可以定义在任何地方,也可以被当作参数进行传递,当作结果进行返回,当作变量被赋值

  5. 函数赋值给变量/常量

    scala 复制代码
    package com.fesco.function
    
    object FunctionDemo1 {
    
      def main(args: Array[String]): Unit = {
    
        // 函数作为"一等公民",可以被定义在任何位置
        def rand(): Int = (Math.random() * 100).toInt
    
        // 调用函数打印结果
        println(rand())
        // 将函数的计算结果赋值给变量/常量
        val r = rand()
        println(r)
        val r2 = rand
        println(r2)
    
        // 不是调用rand函数,而是把rand函数作为数据传递给f
        // 所以此时可以认为,f既是一个常量,也是一个函数
        // 常量f的数据类型是:() => Int
        val f: () => Int = rand _
        println(f)
        // 调用函数f
        println(f())
    
        def sum(x: Int, y: Int): Int = x + y
    
        // 将sum函数作为一个整体数据,赋值给常量s
        // 此时可以认为s既是一个常量,也是一个函数
        // s的数据是一个函数
        // sum的参数类型是(Int, Int),返回值是Int
        // 所以s的数据类型:(Int, Int) => Int
        val s: (Int, Int) => Int = sum _
        // 打印常量s的数据
        println(s)
        // 调用s中的函数
        println(s(3, 5))
    
        // 匿名函数
        val a1: (Int, Int) => Int = (x: Int, y: Int) => x + y
        println(a1)
        println(a1(2, 4))
        // 定义常量a2的时候,已经指定了要封装的参数的类型,在定义函数的时候就可以省略参数类型
        val a2: (Int, Int) => Int = (x, y) => x + y
        println(a2)
        println(a2(2, 4))
    
      }
    
    }

高阶函数

  1. 当一个函数的参数是另一个函数,或者返回值是另一个函数的时候,这个函数就是高阶函数

  2. 将函数作为参数进行传递

    scala 复制代码
    package com.fesco.function
    
    object FunctionDemo2 {
    
      def main(args: Array[String]): Unit = {
    
        // 需求:定义一个函数,对两个整数进行计算,返回一个整数
        /*
          1. 明确参数
            a. 两个整数是未知的,所以需要以参数形式来体现
            b. 对这俩整数来进行计算,计算规则是未知的,所以同样,需要将计算规则以参数形式来体现
          2. 计算逻辑
            要利用传入的规则对两个参数来进行计算
         */
        def calcInt(x: Int, y: Int, f: (Int, Int) => Int): Int = f(x, y)
    
        // 定义计算规则
        def times(x: Int, y: Int): Int = x * y
        // 调用calcInt函数,传入参数和规则
        println(calcInt(3, 6, times))
    
        // 新规则
        def subtract(x: Int, y: Int): Int = x - y
        // 传入规则
        println(calcInt(3, 6, subtract))
    
        // 可以直接传入规则
        println(calcInt(3, 6, (x: Int, y: Int) => {
          x - y
        }))
        // 当函数体只有一句的时候,{}可以省略
        println(calcInt(3, 6, (x: Int, y: Int) => x - y))
        // 定义calcInt函数的时候,就已经指定了规则f中的参数类型和结果类型,所以参数类型可以省略
        println(calcInt(3, 6, (x, y) => x - y))
        // 在匿名函数中,依次调用了参数,并且只调用一次,那么可以省略参数列表不写,用_来依次代替每一个参数
        println(calcInt(3, 6, _ - _))
        println(calcInt(3, 6, _ * _))
      }
    
    }
  3. 将函数作为结果进行返回

    scala 复制代码
    package com.fesco.function
    
    object FunctionDemo3 {
    
      def main(args: Array[String]): Unit = {
    
        // 定义一个函数,可以依次传入两个整数求和
        // 可以先传入一个整数,然后再传入另一个整数
        // 如果只有一个参数,可以省略()不写
        // def sum(x: Int): (Int) => Int = {
        // def sum(x: Int): Int => Int = {
        // 如果在定义函数的时候,没有写返回值类型,而是由编译器自动推导,那么返回函数的时候,必须添加_
        /*
        def sum(x: Int) = {
          def add(y: Int): Int = x + y
    
          add _
        }
        */
        // 如果在定义函数的时候,指定了返回值类型,那么返回函数的时候,可以不用添加_
        def sum(x: Int): Int => Int = {
          def add(y: Int): Int = x + y
    
          add
        }
    
    
        val r = sum(5)
        // r接收到是add函数
        println(r)
        // 再传入第二参数
        val n = r(8)
        println(n)
        // 可以第一个参数不变,改变第二个参数
        println(r(6))
    
        // 传入两个参数
        val n2 = sum(3)(6)
        println(n2)
    
      }
    
    }

闭包(closure)

  1. 闭包,指的是一个函数,如果访问了外部变量的值,那么此时这个函数以及它所处的函数,构成了闭包

  2. 闭包的特点:会延长外部函数中变量的生命周期

    scala 复制代码
    package com.fesco.function
    
    object ClosureDemo {
    
      def main(args: Array[String]): Unit = {
    
        // 函数rand中,接收了一个变量n
        // 正常情况下而言,当rand函数执行结束之后,rand函数所占用的内存应该立即释放
        // 因此变量n应该也立即销毁 -> 此时变量n的生命周期随着函数rand的结束而结束
        def rand(n: Int): Int = (Math.random() * n).toInt
    
        println(rand(10))
        println(rand(50))
    
        def random(n: Int) = {
          def r() = (Math.random() * n).toInt
    
          r _
        }
        // 当执行完这句话之后,random函数就应该执行完了
        // random函数执行完成之后,如果变量n随着random函数一起被销毁
        // 那么会导致产生嵌套的r函数无法执行,所以此时n不会被销毁而是被保留
        // 此时n的生命周期不再随着random函数结束而结束
        // n的生命周期被延长,这个过程就称之为闭包
        val result = random(5)
        println(result())
    
      }
    
    }

    柯里化(Currying)

  3. 柯里化指的是将多个参数的函数变成接收单一参数的过程

  4. 案例

    scala 复制代码
    package com.fesco.method
    
    object MethodDemo1 {
    
      def main(args: Array[String]): Unit = {
    
        // 定义一个函数:获取两个数字中较大的那个数字
        // 基本函数
        /*
        def max(a: Int, b: Int): Int = if (a > b) a else b
        println(max(3, 5))
         */
        // 高阶函数
        def max(a: Int) = {
            
            //定义一个函数并返回
          def m(b: Int): Int = if (a > b) a else b
          m _
        }
    
        //第一个函数,返回一个函数
        val r = max(4)
        // 调用返回的函数  
        println(r(2))
        println(r(1))
          
        // 一次调用两个函数  
        println(max(5)(7))
    
        // max中直接嵌套了一个m函数,所有的计算逻辑都是在m函数中完成,之后直接将m函数返回
        // 这个过程称之为柯里化
        def maxCurry(a: Int)(b: Int) = if (a > b) a else b
    
        println(maxCurry(4)(3))
        // 柯里化虽然将闭包的过程进行了简化,但是导致调用的时候必须将参数全部传入
        val mc = maxCurry(3)(5)
        println(mc)
      }
    
    }
  5. 柯里化本质上还是闭包!!!

传名参数

  1. 案例

    scala 复制代码
    package com.fesco.method
    
    object MethodDemo2 {
    
      def main(args: Array[String]): Unit = {
    
        // 定义一个函数,需要接收一个参数,参数的结果导向只要是Int就可以
        // 接收的是一段逻辑,只要这段逻辑的结果是Int就可以
        // f的数据类型是 =>Int,也就意味着,是要逻辑的结果是Int类型就可以
        def twice(f: => Int): Unit = println(f)
    
        twice(3)
        twice(if (3 > 5) 3 else 5)
    
        def add(x: Int, y: Int): Int = x + y
    
        twice(add(3, 5))
    
        // 要求传入的必须是(Int, Int) => Int的函数
        def calc(f: (Int, Int) => Int): Unit = println(f)
        calc(add)
    
    
      }
    
    }
  2. 案例

    scala 复制代码
    package com.fesco.method
    
    object MethodDemo3 {
    
        //高阶函数用处,手动实现while循环
      def main(args: Array[String]): Unit = {
    
        // 打印1-10
        var i = 1
        while (i <= 10) {
          println(i)
          i += 1
        }
    
        println("=" * 50)
    
        // 会发现,对于while循环,可以拆分成两部分
        // 1. 条件 - 在定义while结构的时候,并不知道条件是什么,而是用户在使用的时候来传入。
        //    对于开发人员而言,只需要确定一件事就可以:条件的结果一定是一个Boolean型
        // 2. 执行逻辑 - 在定义while结构的时候,需要执行什么逻辑,开发人员不知道
        //
        def whileLoop(condition: => Boolean)(body: => Unit): Unit = {
          // 判断条件是否成立
          if (condition) {
            // 只要条件成立,那么就执行逻辑
            body
            whileLoop(condition)(body)
          }
        }
    
        var n = 1
        // 如果({}),那么()可以省略
        /*
        whileLoop(n <= 10)({
          println(n)
          n += 1
        })
         */
        whileLoop(n <= 10) {
          println(n)
          n += 1
        }
      }
    
    }

懒加载

  1. 在函数的返回值之前用lazy来修饰,这个函数就会被推迟执行,直到结果被使用的时候,函数才会执行,这个过程就称之为懒加载,或者惰性加载

    scala 复制代码
    package com.fesco.method
    
    object LazyDemo {
    
      def main(args: Array[String]): Unit = {
    
        def minus(a: Int, b: Int): Int = {
          println("running")
          a - b
        }
    
        val m1 = minus(6, 3)
        println("=" * 50)
        // 执行完下句代码,minus函数不执行
        lazy val m2 = minus(7, 2)
        // 打印m2 ===> m2被使用
        // 此时使用minus的返回结果时,才加载执行minus函数
        println(m2)
    
      }
    
    }
  2. 注意:lazy只能修饰val不能修饰var

相关推荐
特种加菲猫2 分钟前
初阶数据结构之栈的实现
开发语言·数据结构·笔记
江-小北4 分钟前
Java基础面试题04:Iterator 和 ListIterator 的区别是什么?
java·开发语言
鸽鸽程序猿18 分钟前
【前端】javaScript
开发语言·前端·javascript
kylin王国25 分钟前
R语言p值矫正整的方法
开发语言·r语言·p值
捂月41 分钟前
Spring Boot 核心逻辑与工作原理详解
java·spring boot·后端
凯子坚持 c42 分钟前
C++之二叉搜索树:高效与美的极致平衡
开发语言·c++
菜鸟起航ing1 小时前
Java中日志采集框架-JUL、Slf4j、Log4j、Logstash
java·开发语言·log4j·logback
Nightselfhurt1 小时前
RPC学习
java·spring boot·后端·spring·rpc
凤枭香1 小时前
Python Scikit-learn简介(二)
开发语言·python·机器学习·scikit-learn
gkdpjj1 小时前
C++优选算法十四 优先级队列(堆)
开发语言·数据结构·c++·算法