五、Scala从入门到精通一一函数式编程(基础)

1、 函数式编程内容及讲课顺序

1.1、函数式编程内容

函数式编程基础
1、函数定义/声明
2、函数运行机制
3、递归 // 难点 [最短路径,邮差问题,迷宫问题]
4、过程
5、惰性函数和异常
函数式编程高级
1、值函数(函数字面量)
2、闭包
3、应用函数
4、抽象控制…

1.2、函数式编程授课顺序

1、在scala中,函数式编程和面向对象编程融合在一起,学习函数式编程式需要oop的知识,同样学习oop需要函数式编程的基础。
2、关系如下图:
在这里插入图片描述
3、授课顺序: 函数式编程基础->面向对象编程->函数式编程高级

2、函数式编程内容

2.1、几个概念的说明

在学习Scala中将方法、函数、函数式编程和面向对象编程明确一下:

1、在scala中,方法函数几乎可以等同(比如他们的定义、使用、运行机制都一样的),只是函数的使用方式更加的灵活多样 [方法转函数]。
2、函数式编程是从编程方式(范式)的角度来谈的,可以这样理解:函数式编程把函数当做一等公民,充分利用函数、 支持的函数的多种使用方式
比如:
在Scala当中,函数是一等公民,像变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量. ,函数的创建不用依赖于类或者对象,而在Java当中,函数的创建则要依赖于类、抽象类或者接口.
3、面向对象编程是以对象为基础的编程方式。
4、在scala中函数式编程和面向对象编程融合在一起了 。

2.2、在学习Scala中将方法、函数、函数式编程和面向对象编程关系分析图

在这里插入图片描述

2.3、函数式编程小结

1、“函数式编程"是一种"编程范式”(programming paradigm)。
2、它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
3、函数式编程中,将函数也当做数据类型,因此可以接受函数当作输入(参数)和输出(返回值)。(增强了编程的粒度)
4、函数式编程中,最重要的就是函数。

3、为什么需要函数

在这里插入图片描述

4、函数的定义

4.1、基本语法

def 函数名 ([参数名: 参数类型], …)[[: 返回值类型] =] {
语句… //完成某个功能
return 返回值
}
1、函数声明关键字为def (definition)
2、[参数名: 参数类型], …:表示函数的输入(就是参数列表), 可以没有。 如果有,多个参数使用逗号间隔
3、函数中的语句:表示为了实现某一功能代码块
4、函数可以有返回值,也可以没有
5、返回值形式1: // def 函数名(参数列表) : 数据类型 = {函数体} // 返回值确定,清晰
6、返回值形式2: // def 函数名(参数列表) = {函数体} // 有返回值, 类型是推断出来的
7、返回值形式3: // def 函数名(参数列表) {函数体} // 无返回值 Unit
8、如果没有return ,默认以执行到最后一行的结果作为返回值

4.2、快速入门案例

def main(args: Array[String]): Unit = {
  val n1 = 10
  val n2 = 20
  println("res=" + getRes(1, 2, '+'))
}
// 定义函数/方法
def getRes(n1: Int, n2: Int, oper: Char) = {
  if (oper == '+') {
    n1 + n2 // 返回
  } else if (oper == '-') {
    n1 - n2
  } else {
    // 返回null
    null
  }
}

5、函数-调用机制

5.1、函数-调用过程

1、为了让大家更好的理解函数调用机制,看一个案例,并画出示意图,这个很重要,比如getSum计算两个数的和,并返回结果
在这里插入图片描述
一个函数/方法在函数/方法体内又调用了本身,我们称为递归调用在这里插入图片描述
当调用test(4) 和 test2(4) 上面两段代码分别输出什么?
递归调用并分析原因
在这里插入图片描述

5.2、函数递归调用的重要的规则和小结

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

5.3、使用Scala递归调用的应用案例

1、斐波那契数
使用递归的方式,求出斐波那契数1,1,2,3,5,8,13…
给你一个整数n,求出它的斐波那契数是多少?

println("fbn的结果是:" + fbn(7))
 def fbn(n: Int): Int = {

    // 分析
    // 1、当n = 1 结果为1
    // 2、当n = 2 结果为1
    // 3、当n>2 时 结果就是前面两个数的和
    if (n == 1 || n == 2) {
      1
    } else {
      fbn(n - 1) + fbn(n - 2)
    }
  }

2、求函数值
已知f(1) = 3 ; f(n)= 2 * f(n-1)+1
请使用递归的思想变成,求出f(n)的值?

  println(f(2))
 def f(n: Int): Int = {
    if (n == 1) {
      3
    } else {
      2 * f(n - 1) + 1
    }
  }

3、猴子吃桃子问题
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个,当到第10天时,想再吃时(还没吃),发现只有一个桃子了。问题:最初有多少个桃子

    思路分析:
    1 day =10 桃子有 1
    2 day = 9 桃子有 2*(day10[1] + 1)
    3 day = 8 桃子有 (day9[4] + 1) * 2
	println("最初有多少个桃子:" + peach(1))
      def peach(day: Int): Int = {
    if (day == 10) {
      1
    } else {
      (peach(day + 1) + 1) * 2
    }
  }

5.4、函数注意事项和细节讨论

1、函数的形参列表可以是多个, 如果函数没有形参,调用时 可以不带()
2、形参列表和返回值的数据类型可以是值类型和引用类型。

object Details01 {
  def main(args: Array[String]): Unit = {
    // 形参列表和返回值列表的数据类型可以是值类型和引用类型
    val tiger = new Tiger
    val tiger2 = test01(10, tiger)
    println(tiger2.name) // jack
    println(tiger.name)
    println(tiger.hashCode()+" "+tiger2.hashCode())
  }
  def test01(n1: Int, tiger: Tiger): Tiger = {
    println("n1=" + n1)
    tiger.name = "jack"
    tiger
  }
}
class Tiger {
  // 一个名字属性
  var name = ""
}

3、Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下,return关键字可以省略
在这里插入图片描述
4、因为Scala可以自行推断,所以在省略return关键字的场合,返回值类型也可以省略
在这里插入图片描述
5、如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然如果你什么都不写,即使有return 返回值为()

def main(args: Array[String]): Unit = {

  println(getSum2(10, 3)) // ()
}
// 如果写了return,返回值类型就不能省略
def getSum(n1: Int,n2:Int):Int = {
  return n1 + n2
}
// 如果返回值这里什么都没有写,即表示该函数没有返回值
// 这时return 无效
def getSum2(n1: Int,n2:Int) {
  return n1 + n2
}

6、如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值

println(GetSum(10,30))
// 如果函数明确声明无返回值(声明为Unit),
// 那么函数体中即使使用return关键字也不会有返回值
def GetSum(n1:Int,n2:Int):Unit={
  return n1 + n2
}

7、如果明确函数无返回值或不确定返回值类型,那么返回值类 型可以省略(或声明为Any)在这里插入图片描述
8、Scala语法中任何的语法结构都可以嵌套其他语法结构(灵活),即:函数/方法中可以再声明/定义函数/方法,类中可以再声明类

def main(args: Array[String]): Unit = {
  def f1(): Unit = { // ok
    println("f1")
  }

  println("ok~~")
  def SayOk(): Unit = { // private final sayOk$1()
  println("main sayok")

  def SayOk(): Unit = { // private final sayOk$2()
    println("main sayok")
      }
    }
}

def SayOk(): Unit = { // 成员
  println("main sayok")
}

在这里插入图片描述
在这里插入图片描述
9、Scala函数的形参,在声明函数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值

def main(args: Array[String]): Unit = {
  println(sayOk("mary"))
}
// name形参的默认值为jack
def sayOk(name: String = "jack"): String = {
  return name +" "+ "ok!"
}

10、如果函数存在多个参数,每个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数[案列演示+练习]

def main(args: Array[String]): Unit = {
  //mysqlCon()
  //mysqlCon("127.0.0.1",7777) // 从左到右覆盖

  // 如果我们希望指定覆盖某个默认值,则使用带名参数即可,
  // 比如修改用户名和密码
  mysqlCon(user = "tom", pwd = "123")

  // f6("v2") // x
  f6(p2 = "v2") // √
}


def mysqlCon(add: String = "localhost", post: Int = 3306,
             user: String = "root", pwd: String = "root"
            ): Unit = {
  println("add=" + add)
  println("post=" + post)
  println("user=" + user)
  println("pwd=" + pwd)
}

def f6(p1: String = "v1", p2: String) {
  println(p1 + p2)
}

11、Scala 函数的形参默认是val的 ,因此不能再函数中修改
12、递归函数为执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型

def f8(n:Int)={ //? 错误,递归不能使用类型推断,必须指定返回的数据类型
    if(n <= 0)
    1
    else
    n * f8(n - 1)
}

13、Scala 函数支持可变参数
// 支持0到多个参数
def sum(args: Int) : Int = {
}
// 支持1到多个参数
def sum(n1:Int,args: Int*) : Int = {
}
说明:
1、args是集合,通过for循环可以访问到各个值
2、案列演示,编写一个函数sum,可以求出1 到 多个Int的和
3、可变参数需要写在形参列表的最后。
在这里插入图片描述

 // 编写一个函数sum,可以求出1 到 多个Int的和
  println(sum(10, 30, 40, 3, 45, 7))
}

//
def sum(n1: Int, args: Int*): Int = {
  println("args.length:" + args.length)
  // 遍历
  var sum = n1
  for (item <- args) {
    sum += item
  }
  sum
}

/*// 可变参数需要放在最后
def sum2( args: Int*,n1: Int): Int =*/

5.5、函数练习题

object Hello01 {
    def main(args: Array[String]): Unit = {
        def f1 = "venassa" 
        println(f1) //  venassa
    }
}

6、过程

6.1、基本概念

将函数的返回类型为Unit的函数称之为过程(procedure),如果明确没有返回值,那么等号可以省略
案例说明:

// f10 没有返回值,可以使用Unit来说明
// 这时,这个函数我们也叫过程(procedure)
def f10(name:String) : Unit = {
    println(name + "Hello")
}

6.2、注意事项

1)注意区分:如果函数声明时没有返回值类型,但是有=号,可以进行类型推断最后一行代码。这时这个函数实际是没有返回值的,该函数并不是过程。(把=号去掉的)
2)开发工具的自动代码补全功能,虽然会自动加上Unit,但是考虑到Scala语言的简单,灵活,最好不加

7、惰性函数

7.1、看一个应用场景

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

7.2、画图说明[大数据推荐系统]

在这里插入图片描述

7.3、Scala中实现懒加载的代码

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

def main(args: Array[String]): Unit = {
  lazy val res = sum(10, 20)
  println("··················")
  println("res = " + res) // 在要使用res前,才执行
}
// sum函数,返回和
def sum(n1: Int, n2: Int) = {
  println("sum()执行了") // 输出一句话
  n1 + n2
}

7.4、注意事项和细节说明:

1、lazy不能修饰var类型的变量
在这里插入图片描述
2、不但是 在调用函数时,加了 lazy ,会导致函数的执行被推迟,我们在声明一个变量时,如果给声明了 lazy ,那么变量值得分配也会推迟。 比如 lazy val i = 10
在这里插入图片描述

8、异常处理

8.1、介绍

1、Scala提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有任意数量的try…catch块。
2、语法处理上和Java类似,但是又不尽相同

8.2、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");
        }
        System.out.println("ok···继续执行");
    }
}

8.3、 Java异常处理的注意点

1、Java语言按照try-catch-catch…–finally的方式来处理异常
2、不管有没有异常捕获,都会执行finally,因此通常可以在finally代码块中释放资源
3、可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示“Exception ‘java.lang.xxxxx’has already been caught "[案列演示]
在这里插入图片描述

8.4、Scala异常处理举例

object ScalaExceptionDemo {
  def main(args: Array[String]): Unit = {
    try {
      val r = 10 / 0
    } catch {
      // 说明
      // 1.在Scala中只有一个catch
      // 2.在catch中有多个case,每个case可以匹配一种异常
      // 3. => 关键符号,表示后面是对该异常的处理代码块
      // 4. finally 最终要执行的
      case ex: ArithmeticException => {
        println("捕获了除数为0的算术异常")
      }
      case ex: Exception => println("捕获了异常")
    } finally {
      // 最终要执行的代码
      println("Scala finally")
    }
    println("ok···继续执行")
  }
}

8.5、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注释来声明异常
在这里插入图片描述

9、函数的课堂练习题

1、编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表:
在这里插入图片描述

object Exercise01 {
  def main(args: Array[String]): Unit = {
    printf("请输入一个数字(1~9)之间:")
    val n = StdIn.readInt()
    math(n)
  }
  // 编写一个函数,输出99乘法表
  def math(n: Int) = {
    for (i <- 1 to n) {
      for (j <- 1 to i) {
        printf("%d * %d = %d\t", j, i, i * j)
      }
      println()
    }
  }
}
发布了15 篇原创文章 · 获赞 20 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_44258756/article/details/105214377