Scala函数式编程

函数式编程

面向对象和面向过程的区别

  1. 设计思路——面向过程的编程以事件或任务为中心,关注于如何通过一系列步骤来完成特定的任务。它强调的是任务执行的顺序和流程。相比之下,面向对象的编程则以对象为中心,将数据和相关操作封装在一起,通过对象之间的交互来完成任务。面向对象设计强调的是对象的职责和交互
  2. 代码组织——在面向过程的编程中,代码通常按照功能模块或过程来组织,每个过程负责一部分特定的任务。而在面向对象的编程中,代码围绕对象来组织,每个对象包含数据(属性)和行为(方法),对象的设计反映了现实世界中的实体和概念。
  3. 抽象层次——面向过程的编程通常关注于具体的操作和算法,而面向对象的编程则更侧重于高层次的抽象,通过类和对象来模拟现实世界的实体和它们之间的关系。
  4. 可维护性——面向过程的代码由于其紧密耦合的特点,修改一处可能会影响到整个系统。而面向对象的代码通常更容易维护和扩展,因为对象内部的修改不会影响到其他对象。
  5. 总——面向过程适用于解决明确、线性的问题,而面向对象则更适合处理复杂的、非线性的、需要高度抽象和模块化的问题。在实际开发中,两者往往会结合使用,以达到最佳的开发效果。

函数式编程是一种将计算视为函数运算的编程范式。它的核心在于使用函数作为主要的构建块,并强调没有副作用的函数和不可变性的数据结构,函数式编程尽量避免在程序中使用共享的状态和可变的数据,这有助于减少并发问题和提高程序的可预测性。不可变性的特点特别适合大数据的分布式并行处理的特点。

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

def sum( x : Int , y : Int ) : Int = {

x + y

}

// def是定义函数的关键字,sum是函数的函数名,x、y是参数名,Int是参数类型,Int是函数返回值类型,x+y是函数体。

def fdk (name : String ) : String = {

println(我是 + name)

Return 你好 name

}

fkd (name = fdk )    //赋值name

println( fdk ( ) )     //打印出方法和返回值

(name : String )是参数,你好 name是返回值

函数参数存在可变参数,如果参数列表存在多个参数,那么可变参数一般放置在最后。

可变参数在数据类型后面加 *

def f1 ( str : String* ) : Unit = {

如果参数列表存在多个参数,那么可变参数一般放置在最后,传入参数时易于判断归属

def f2 ( str1 : String , str2 : String*) : Unit = {

参数默认值,一般将有默认值的参数放置在参数列表的后面

def f3(name: String = "atguigu"): Unit = {    //定义默认值参数atguigu
     println("My school is" + name)
  }
  f3(name="school")
  f3()                                           //当没有传入数值时,值为默认值

带命参数

  ef f4(name: String, age: Int): Unit = {
      println(s"${age}岁的${name}在电专学习")
  }
  f4(name="alice", age=20)
  f4(age=20,name="bob")       //直接对参数名称传入数据,不用再按顺序

函数至简原则

  1. return可以省略,Scala会使用函数体的最后一行代码作为返回值

def f1(name: String): String = {
      name
  }
  println(f1( name = "atguigu" ))

  1. 如果函数体只有一行代码,可以省略大括号

def f2(name: String): String = name

println(f2( name = "atguigu" ))

  1. 返回值类型如果能推断出来,那么可以省略

def f3(name: String) = name

println(f3( name = "atguigu" ))

  1. 如果有return,则不能省略返回值类型,必须指定

 def f4(name: String) : String = {    //必须加返回数据类型,否则返回数据类型为Nothing

return name                    //用了return

  }

println(f4( name = "atguigu" )) 

  1. 如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用

 def f5(name: String) : Unit = {

return name                   //返回一个空括号,return不起作用

} 

  1. Scala如果期望是无返回值类型,可以省略等号

def f6(name: String){        //不给返回值类型,就是无返回值类型,空括号

Println(name)

}

  1. 如果参数无参,但是声明了参数列表,那么调用时,小括号,可以不加

def f7() : Unit = {
      println("atguigu" )
  }
  f7()                                 //正常调用
  f7                                  //不加小括号的调用,正常运行

  1. 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略

def f8 : Unit = {                   //省略了函数名后的小括号
      println("atguigu" )
  }
  f8()                               //调用时小括号必须省略,否则会报错
  f8

  1. 如果不关心名称,只关心逻辑处理,那么函数名可以省略

(name: String) => { println(name) }      //匿名函数,lambda表达式

匿名函数的简化原则

定义了一个函数,要处理的数据定死,传入操作,以函数作为参数输入。

def f(func : String => Unit) :Unit = {
      func("atguigu")                             //数据定死
  }
      f ( (name:String) => { println(name) } )     //传入操作

  1. 参数的类型可以省略,会根据形参进行自动的推导

f( (name) => {println ( name } )

  1. 类型省略之后,发现只有一个参数,则圆括号可以省略,没有参数或多个参数,圆括号不能省略。

f( name => {println ( name } )

  1. 匿名函数如果只有一行,则大括号可以省略

f( (name) => println ( name )

  1. 如果参数只出现一次,则参数省略且后面参数可以一用_代替

f( println (_) )

  1. 如果可以判断出,当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线

f( println )

匿名函数

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

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

x为参数名称,Int为数据类型

实际实例,定义一个“二元运算”函数,只操作1和2两个数,具体运算通过参数传入

def F1(fun: (Int,Int)=>Int ):Int ={
      fun(1,2)
  }
  val add = (a:Int,b:Int) => a+b
  val minus= (a:Int,b:Int) => a-b
  println(F1(add))
  println(F1(minus))

简化上方匿名函数

println( F1( (a:Int,b:Int) => a+b) )      //一次简化

println( F1( (a,b) => a+b) )           //二次简化

println( F1( _+_ ) )                 //三次简化

函数高阶用法

  1. 函数作为值进行传递,f是一个函数

val f1: Int=>Int = f       //第一种写法,f1是新函数,第一个Int是参数的类型,第二个Int是返回值的类型,f是被接收的函数

val f2 = f _             //第二种写法,f2是新函数,f后面加下划线,表示f是个函数,而不是其他

  1. 函数作为参数进行传递

参数定义死,方法传入的以前以学

定义一个参数和方法都传入的二元计算函数

def dualEval(op: (Int, Int)=>Int,a:Int,b:Int):Int={
      op(a,b)
  }

传入数据和方法的方式

    1. 先定义方法,再将具体数据传入方法,,方法再传入函数

def add(a:Int,b:Int): Int={

a + b

}

println(dualEval(add,12,35))

    1. 将上面的方法简化,再传入数值

println(dualEval( (a , b) => a+b ,12,35) )

    1. 再一步将上面方法简化

println(dualEval(_+_,12,35)) 

3.函数作为函数的返回值返回

def f5():Int=>Unit = {
      def f6(a:Int):Unit = {
          println("f6调用"+a)
      }
      f6         //将函数直接返回
  }

第一种调用方法

 val f6=f5()

println(f6)
  println(f6(25))
 

第二种调用方法

println(f5()(25))

对数组进行处理,将操作抽象出来,处理完毕之后返回一个新的数组

val arr:Array[Int]=Array(11,21,31,41,51)

//对数组进行处理,将操作抽象出来,处理完毕之后的结果返回一个新的数组

def sss(ar:Array[Int],op:Int=>Int):Array[Int]={
  for(elem<-ar)yield op(elem)
  }

思路:传入两个参数,数组和方法,再在函数中运算,再输入进一个新的数组中。

首先,定义了一个整数数组arr,包含5个元素:11, 21, 31, 41, 51。

接着,定义了一个名为sss的函数,它接受两个参数:一个整数数组ar和一个操作函数op。这个函数的作用是对数组ar中的每个元素应用操作函数op,并返回一个新的数组。

//定义一个加一操作

def addOne(elem:Int):Int={
      elem + 1
  }

//调用函数

val newArray:Array[Int] = sss(arr,addOne)

println(newArray.mkString ( "," ) )

然后,定义了一个名为addone的函数,它接受一个整数参数elem,并返回elem+1。这个函数的作用是将输入的整数加1。

接下来,调用sss函数,将数组arr和函数addone作为参数传入。这将对数组arr中的每个元素加1,并将结果存储在新的数组newArray中。最后,打印出新数组newArray。

//传入匿名函数,实现元素翻倍

val newArray2 = sss(arr,_ * 2)

println(newArray2.mkString(","))

上述方法的简化,调用sss函数,这次传入一个匿名函数elem => elem * 2,它的作用是将输入的整数翻倍。这将对数组arr中的每个元素翻倍,并将结果存储在新的数组newArray2中。

题:定义一个函数func,它接收一个Int类型的参数,返回一个函数(记作f1),它返回的函数f1,接收一个String类型的参数,同样返回一个函数(记作f2),函数f2接收一个Char类型的参数,返回一个Boolean的值

要求调用函数func(0)(“”)(‘0’)得到的返回值为false,其他情况均返回true

正常写法为:

def func(i:Int):String=>(Char=>Boolean)= {
      def f1(s: String): Char => Boolean = {
          def f2(c:Char): Boolean={
              if (i==0 && s=="" && c=='0') false else true
          }
          f2
      }
      f1
  }
  println( func(0)("")('0') )

简化后的写法为:

def func(i:Int):String=>(Char=>Boolean)= {
      s => c => if (i==0 && s=="" && c=='0') false else true
  }
  println( func(0)("")('0') )

柯里化,在实际应用中,最常用的方式,数据名后面加的是传入数据类型,括号后面的Boolean是最终返回数据类型

def func(i:Int)(s:String)(c:Char):Boolean= {     

      if (i==0 && s=="" && c=='0') false else true
  }
  println( func(0)("")('0') )

函数柯里化&闭包

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

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

闭包的经典用法:

def addByFour(a:Int):Int=>Int ={
      def addb(b:Int):Int = {
            a+b
      }
      addb
  }

分层输入两个数,平衡了适用性和通用性

可以将第一层的量作为定量,第二层的量作为变量,这样就扩展了通用性。

用lambda表达式实现

def addByA1(a:Int):Int=>Int={
      (b:Int) =>{
          a+b
      }
  }

简写上方函数

def addByA2(a:Int):Int=>Int = a + _

柯里化

def addCurrying(a:Int)(b:Int):Int={
      a + b
  }

闭包和柯里化是绑定的,柯里化的底层一定是闭包,闭包不一定是柯里化

递归

一个函数/方法在函数/方法体中又调用了本身,我们称之为递归调用,在递归调用中一定要有一个基准情形(退出机制)

    递归的问题,在计算时中会占用大量栈空间

递归实现计算阶乘

def fact(n:Int):Int={
      if(n==0)return 1         //基准情形,return需要放在这里
      fact(n-1) * n            //调用自身,实现阶乘
  }

尾递归实现

将递归的结果赋给一个变量,减少栈消耗

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,currRes =1)
  }

函数体中使用了@tailrec注解,表示该函数使用了尾递归优化,函数内部定义了一个名为Loop的辅助函数,用于实现尾递归,Loop函数接受两个参数,一个是当前的计数器n,另一个是当前的累加结果currRes,在Loop函数中,如果n等于0,则返回currRes;否则递归调用Loop函数,将n减1,并将currRes乘以n作为新的累加结果。

控制抽象

def f1():Int={                           //被调用的函数
      println("f1调用")
      12
  }

值调用:把计算之后的传递过去(之后函数中就是一个值)

def f0(a:Int):Unit={
      println("a:"+a)                               //打印出来为:
      println("a:"+a)                               //f1调用
  }                                                //a:12
  f0(f1())                                         //a:12

名调用:把代码传递过去,传递的不再是具体的值,而是代码块(之后函数中就是一个代码块,像一个脚本)

def f2(a: =>Int):Unit={                        //打印出来为:
      println("a:" + a)                           //f1调用
      println("a:" + a)                           //a:12
  }                                              //f1调用
  f2(f1())                                       //a:12

惰性加载

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

def sum(a:Int,b:Int):Int={                       //打印出来为:
      println("3.调用sum")                       //1.函数调用
      a+b                                        //3.调用sum
  }                                              //2.result=60
  lazy val result:Int= sum(13,47)             
  println("1.函数调用")
  println("2.result="+result)

result常量调用函数sum时,没有立刻执行,而是等到要使用result的时候,再去执行其中的操作(如拖延症,不到最后一刻绝不行动)

  • 29
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值