zoukankan      html  css  js  c++  java
  • Scala函数式编程基础

    [toc]

    ## Scala函数式编程基础

    ### 1. 函数式编程内容

    > 1. `函数式编程基础`
    > 1. 函数定义/声明
    > 2. 函数运行机制
    > 3. 递归
    > - `难点 [最短路径,邮差问题,迷宫问题, 回溯]`
    > 4. 过程
    > 5. 惰性函数和异常
    > 2. `函数式编程高级`
    > 1. 值函数(函数字面量)
    > 2. 高阶函数
    > 3. 闭包
    > 4. 应用函数
    > 5. 柯里化函数,抽象控制...
    > 3. 在Scala中,函数式编程和面向对象编程融合在一起,学习函数式编程式需要OOP的知识,同样学习OOP需要函数式编程的基础。
    > 4. 学习顺序`: 函数式编程基础->面向对象编程->函数式编程高级;`

    ### 2. 函数式编程介绍

    > 在学习Scala中将`方法、函数、函数式编程和面向对象编程`明确一下:
    >
    > 1. 在scala中,方法和函数几乎可以等同(比如他们的定义、使用、运行机制都一样的),`只是函数的使用方式更加的灵活多样`。
    >
    > 2. 函数式编程是从编程方式(范式)的角度来谈的,可以这样理解:`函数式编程把函数当做一等公民`,充分利用函数、 支持的函数的多种使用方式。
    >
    > 1. 比如:在Scala当中,函数是一等公民,像变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量. ,函数的创建不用依赖于类或者对象,而在Java当中,函数的创建则要依赖于类、抽象类或者接口。
    >
    > 3. 面向对象编程是以对象为基础的编程方式。
    >
    > 4. 在Scala中函数式编程和面向对象编程融合在一起了 。
    >
    > 5. 在学习Scala中将方法、函数、函数式编程和面向对象编程关系分析图:
    >
    > ![image-20210325145202072](assets/image-20210325145202072.png)
    >
    > 6. 小结:
    >
    > 1. "函数式编程"是一种"编程范式"(programming paradigm)。
    > 2. 它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
    > 3. 函数式编程中,将函数也当做数据类型,因此可以接受函数当作输入(参数)和输出(返回值)。
    > 4. 函数式编程中,最重要的就是函数。

    ### 3. 函数定义/声明

    为完成某一功能的程序指令(语句)的集合,称为函数。

    ~~~~scala
    def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] {
    语句...
    return 返回值
    }
    ~~~~

    > 1. 函数声明关键字为def (definition)
    > 2. [参数名: 参数类型], ...:表示函数的输入(就是参数列表), 可以没有。 如果有,多个参数使用逗号间隔
    > 3. 函数中的语句:表示为了实现某一功能代码块
    > 4. 函数可以有返回值,也可以没有
    > 1. 返回值形式1: `: 返回值类型 =`
    > 2. 返回值形式2: `=` 表示返回值类型不确定,使用类型推导完成
    > 3. 返回值形式3: 表示没有返回值,return 不生效
    > 5. `如果没有return ,默认以执行到最后一行的结果作为返回值。`
    > 6. 快速入门案例:

    ~~~~scala
    /**
    * @Date 2021/3/24 20:31
    * @Version 10.21
    * @Author DuanChaojie
    */
    object FunDemo01 {
    def main(args: Array[String]): Unit = {
    val n1 = 10
    val n2 = 20
    val oper = '-'
    val unit = getRes(n1, n2, oper)
    println(unit)
    }

    def getRes(n1: Int, n2: Int, oper: Char) = {
    if (oper == '+') {
    } else if (oper == '-') {
    println("res=" + (n1 - n2))
    }
    null
    }
    }
    ~~~~

    ### 4. 函数运行机制

    为了让大家更好的理解函数调用机制, 看一个案例,并画出示意图,这个很重要,比如`getSum` 计算两个数的和,并返回结果。

    ![image-20210325145908810](assets/image-20210325145908810.png)

    ### 5. ☆递归☆

    > 函数递归需要遵守的重要原则(总结):
    >
    > 1. 程序执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
    > 2. 函数的局部变量是独立的,不会相互影响
    > 3. 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)
    > 4. 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁。

    #### Scala递归课堂练习题

    ~~~~scala
    /**
    * @Date 2021/3/24 20:41
    * @Version 10.21
    * @Author DuanChaojie
    */
    object RecursiveExercise {
    def main(args: Array[String]): Unit = {
    val res1 = fib(6)
    println(res1) //8
    val res2 = fun(2)
    println(res2) //7
    val res3 = peach(1)
    println(res3) //1534
    }

    /**
    * 题1:斐波那契数
    * 请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13...
    * 给你一个整数n,求出它的斐波那契数是多少?
    */
    def fib(n: Int): Int = {
    if (n == 1 || n == 2) {
    return 1
    }
    return fib(n - 1) + fib(n - 2)
    }

    /**
    * 题2:求函数值
    * 已知 f(1)=3; f(n) = 2*f(n-1)+1;
    * 请使用递归的思想编程,求出 f(n)的值?
    */
    def fun(n: Int): Int = {
    if (n == 1) {
    return 3
    }
    return 2 * fun(n - 1) + 1
    }

    /**
    * 题3:猴子吃桃子问题 有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
    * 以后每天猴子都吃其中的一半,然后再多吃一个。
    * 当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子?
    */
    def peach(day: Int): Int = {
    if (day == 10) {
    return 1
    } else {
    return (peach(day + 1) + 1) * 2
    }
    }

    }
    ~~~~

    ### 6. Scala函数注意事项和细节讨论☆

    > 1. 函数的形参列表可以是多个, 如果函数没有形参,调用时 可以不带()
    >
    > 2. 形参列表和返回值列表的数据类型可以是值类型和引用类型。
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 20:57
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails01 {
    > def main(args: Array[String]): Unit = {
    > val pig1 = new Pig
    > val pig2 = toPig(19,pig1)
    > println(pig2.name)
    >
    > //true
    > println(pig1.hashCode() == pig2.hashCode())
    > }
    >
    > def toPig(n: Int, pig: Pig): Pig = {
    > if (n >= 18) {
    > pig.name = "大胖猪"
    > }
    > return pig
    > }
    > }
    >
    > class Pig {
    > var name = "小胖猪"
    > }
    > ~~~~
    >
    > 3. Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下,return关键字可以省略。
    >
    > ~~~scala
    > def getSum1(n1: Int, n2: Int): Int = {
    > n1 + n2
    > }
    > ~~~
    >
    > 4. 因为Scala可以自行推断,所以在省略return关键字的场合,返回值类型也可以省略
    >
    > ~~~~scala
    > def getSum1(n1: Int, n2: Int) = {
    > n1 + n2
    > }
    > ~~~~
    >
    > 5. 如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然如果你什么都不写,即使有return 返回值为()
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 21:04
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails02 {
    > def main(args: Array[String]): Unit = {
    > val sum1 = getSum1(1, 2)
    > println(sum1)
    > val res = getRes(3, 4)
    > //返回为 ()
    > println(res)
    > val sum2 = getSum2(5, 6)
    > //返回为 ()
    > println(sum2)
    > }
    >
    > // 如果写了return ,返回值类型就不能省略
    > def getSum1(n1: Int, n2: Int): Int = {
    > return n1 + n2
    > }
    >
    > /**
    > * 如果返回值这里什么什么都没有写,即表示该函数没有返回值
    > * 这时return无效
    > */
    > def getRes(n1: Int, n2: Int) {
    > return n1 + n2
    > }
    > }
    > ~~~~
    >
    > 6. 如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值。
    >
    > ~~~~scala
    > /**
    > * 如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return 关键字也不会有返回值
    > */
    > def getSum2(n1: Int, n2: Int): Unit = {
    > return n1 + n2
    > }
    > ~~~~
    >
    > 7. 如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为Any)
    >
    > 8. Scala语法中任何的语法结构都可以嵌套其他语法结构(灵活),即:函数中可以再声明/定义函数,类中可以再声明类 ,方法中可以再声明/定义方法。
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 21:15
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails03 {
    > def main(args: Array[String]): Unit = {
    >
    > // 底层private final
    > def main(): Unit = {
    > println("main ~")
    > }
    >
    > println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    >
    > // 底层 private final sayHello$1()
    > def sayHello(): Unit = {
    > println("main say hello")
    >
    > // 底层 private final sayHello$2()
    > def sayHello(): Unit = {
    > println("say hello hello")
    > }
    > }
    >
    > }
    >
    > def sayHello(): Unit = {
    > println("say hello")
    > }
    > }
    > ~~~~
    >
    > 9. Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值。
    >
    > ~~~scala
    > /**
    > * @Date 2021/3/24 22:08
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails04 {
    > def main(args: Array[String]): Unit = {
    > println(sayOK())
    > println(sayOK("tom"))
    > }
    >
    > def sayOK(name: String = "jack"): String = {
    > return name + "\tok!"
    > }
    > }
    > ~~~
    >
    > 10. 如果函数存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数。
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 22:10
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails05 {
    > def main(args: Array[String]): Unit = {
    > mysqlCon()
    > println("--------------------------")
    > // 从左到右覆盖
    > mysqlCon("127.0.0.1", 777)
    > println("--------------------------")
    > // 如果我们希望指定覆盖某个默认值,则使用带名参数即可,比如修改用户名和密码
    > mysqlCon(user = "ddaimm", pwd = "1021")
    >
    >
    > println("**************************")
    >
    > // fun("v2") 报错
    > fun(p2 = "v2")
    > }
    >
    > def mysqlCon(add: String = "localhost", port: Int = 3306,
    > user: String = "root", pwd: String = "root"): Unit = {
    > println("add=" + add)
    > println("port=" + port)
    > println("user=" + user)
    > println("pwd=" + pwd)
    > }
    >
    > def fun(p1: String = "v1", p2: String) {
    > println(p1 + p2)
    > }
    > }
    > ~~~~
    >
    > 11. Scala 函数的形参默认是val的,因此不能在函数中进行修改。
    >
    > 12. Scala函数支持可变参数。
    >
    > ~~~~scala
    > /**
    > * args 是集合, 通过 for循环 可以访问到各个值。
    > * 案例演示: 编写一个函数sum ,可以求出 1到多个int的和
    > * 可变参数需要写在形参列表的最后。
    > */
    > object FunVarParameters {
    > def main(args: Array[String]): Unit = {
    > val res = sum(1, 2, 3, 4, 5, 5)
    > println(res)
    > }
    >
    > /**
    > *
    > * @param n
    > * @param args 可变参数需要放到最后
    > * @return
    > */
    > def sum(n: Int, args: Int*): Int = {
    > println("args.length = " + args.length)
    > var sum = n
    > for (item <- args) {
    > sum += item
    > }
    > sum
    > }
    > }
    > ~~~~
    >
    > 13. 函数练习题
    >
    > ~~~~scala
    > object FunDetails06 {
    > def main(args: Array[String]): Unit = {
    > println(f1)
    > println(f2)
    > }
    >
    > //输出 venassa
    > def f1 = "venassa" //等价于
    >
    > def f2() = {
    > "venassa"
    > }
    > }
    > ~~~~

    ### 7. 过程

    > 1. `将函数的返回类型为Unit的函数称之为过程(procedure)`,如果明确函数没有返回值,那么等号可以省略。
    > 2. 注意事项和细节说明:
    > 1. 注意区分: 如果函数声明时没有返回值类型,但是有 = 号,可以进行类型推断最后一行代码。这时这个函数实际是有返回值的,该函数并不是过程。
    > 2. 开发工具的自动代码补全功能,虽然会自动加上Unit,但是考虑到Scala语言的简单,灵活,最好不加。

    ### 8. 惰性函数

    > 1. `惰性计算`(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。首先,您可以将耗时的计算推迟到绝对需要的时候。其次,您可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。==Java 并没有为惰性提供原生支持,Scala提供了。==
    >
    > 2. 大数据推荐系统:
    >
    > ![image-20210325152730944](assets/image-20210325152730944.png)

    #### Java实现懒加载的代码

    ~~~scala
    /**
    * @Date 2021/3/25 15:30
    * @Version 10.21
    * @Author DuanChaojie
    * 比如常用的单例模式懒汉式实现时就使用了上面类似的思路实现
    */
    public class LazySingleton {
    private String property; //属性也可能是一个数据库连接,文件等资源

    public String getProperty() {
    if (property == null) {//如果没有初始化过,那么进行初始化
    property = initProperty();
    }
    return property;
    }

    private String initProperty() {
    return "property";
    }
    }
    ~~~

    #### 惰性函数

    ==当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。==这种函数我们称之为惰性函数,在Java的某些框架代码中称之为懒加载(延迟加载)。

    ~~~~scala
    /**
    * @Date 2021/3/24 22:31
    * @Version 10.21
    * @Author DuanChaojie
    */
    object LazyDemo01 {
    def main(args: Array[String]): Unit = {
    lazy val res = sum(10, 20)
    println("-----------------")
    println("res=" + res) //在要使用res前,才执行
    }
    def sum(n1: Int, n2: Int): Int = {
    println("sum() 执行了..")
    return n1 + n2
    }
    }
    ~~~~

    > 注意事项和细节:
    >
    > 1. `lazy 不能修饰 var 类型的变量。`
    > 2. 不但是 在调用函数时,加了 lazy ,会导致函数的执行被推迟,我们在声明一个变量时,如果给声明了 lazy,那么变量值得分配也会推迟。 比如 `lazy val i = 10`

    ### 9. 异常

    > 1. Scala提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有任意数量的try...catch块。
    >
    > 2. `语法处理上和Java类似,但是又不尽相同`
    >
    > 3. Java异常处理:
    >
    > ~~~~java
    > public class JavaExceptionDemo01 {
    > public static void main(String[] args) {
    > try {
    > // 可疑代码
    > int i = 0;
    > int b = 10;
    > int c = b / i; // 执行代码时,会抛出ArithmeticException异常
    > } catch (Exception e) {
    > e.printStackTrace();
    > } finally {
    > // 最终要执行的代码
    > System.out.println("java finally");
    > }
    >
    > }
    > }
    > ~~~~
    >
    > 4. Java异常处理的注意点:
    >
    > 1. java语言按照try—catch-catch...—finally的方式来处理异常
    > 2. 不管有没有异常捕获,都会执行finally, 因此通常可以在finally代码块中释放资源。
    > 3. 可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示 "`Exception 'java.lang.xxxxxx' has already been caught`"【案例演示】
    >
    > 5. Scala异常处理举例:
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 22:45
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object ScalaExceptionDemo01 {
    > def main(args: Array[String]): Unit = {
    > //说明
    > //1. 在scala中只有一个catch
    > //2. 在catch中有多个case, 每个case可以匹配一种异常 case ex: ArithmeticException
    > //3. => 关键符号,表示后面是对该异常的处理代码块
    > //4. finally 最终要执行的
    > try {
    > val r = 10 / 0
    > } catch {
    > case ex: ArithmeticException => {
    > println("捕获了除数为零的算数异常")
    > ex.printStackTrace()
    > }
    > case ex: Exception => println("捕获了异常")
    > } finally {
    > // 最终要执行的代码
    > println("scala finally...")
    > }
    > }
    > }
    > ~~~~

    #### Scala异常处理小结

    > 1. 我们将`可疑代码封装在try块中`。 在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止。
    > 2. Scala的异常的工作机制和Java一样,`但是Scala没有“checked(编译期)”异常`,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。
    > 3. 用`throw关键字,抛出一个异常对象`。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。
    > 4. 在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case子句来匹配异常。【`前面案例可以看出这个特点, 模式匹配我们后面详解`】,当匹配上后 => 有多条语句可以换行写,类似 java 的 switch case x: 代码块..
    > 5. 异常捕捉的机制与其他语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,`越具体的异常越要靠前,越普遍的异常越靠后`,如果把越普遍的异常写在前,把具体的异常写在后,`在scala中也不会报错,但这样是非常不好的编程风格。`
    > 6. finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样。
    > 7. Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息。 它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在Scala中,可以使用throws注释来声明异常

    ~~~~scala
    object ThrowsComment {
    def main(args: Array[String]): Unit = {
    f11()
    }

    @throws(classOf[NumberFormatException]) //等同于Java NumberFormatException.class
    def f11() = {
    "abc".toInt
    }
    }
    ~~~~

    ### 10. 函数的课题练习

    ~~~~scala
    /**
    * @Date 2021/3/24 22:52
    * @Version 10.21
    * @Author DuanChaojie
    */
    object FunExercise {
    def main(args: Array[String]): Unit = {
    getTriangle(4)

    getMuTable(11)
    }

    /**
    * 函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金子塔。
    */
    def getTriangle(n: Int): Unit = {
    for (i <- 1 to n) {
    for (j <- 1 to n - i) {
    print(" ")
    }
    for (k <- 1 to 2 * i - 1) {
    print("*")
    }
    println()
    }
    }

    /**
    * 编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表
    */
    def getMuTable(n: Int): Unit = {
    for (i <- 1 to n) {
    for (j <- 1 to i) {
    print(s"$j * $i = ${i * j}\t")
    }
    println()
    }
    }
    }
    ~~~~

    ## ☆

  • 相关阅读:
    POJ 2236 Wireless Network(并查集)
    POJ 2010 Moo University
    POJ 3614 Sunscreen(贪心,区间单点匹配)
    POJ 2184 Cow Exhibition(背包)
    POJ 1631 Bridging signals(LIS的等价表述)
    POJ 3181 Dollar Dayz(递推,两个long long)
    POJ 3046 Ant Counting(递推,和号优化)
    POJ 3280 Cheapest Palindrome(区间dp)
    POJ 3616 Milking Time(dp)
    POJ 2385 Apple Catching(01背包)
  • 原文地址:https://www.cnblogs.com/huaobin/p/15764412.html
Copyright © 2011-2022 走看看