美文网首页语言学习——kotlin人民广场
Kotlin——函数和Lambda表达式

Kotlin——函数和Lambda表达式

作者: So_ProbuING | 来源:发表于2021-04-28 16:07 被阅读0次

    函数是执行特定任务的一段代码。程序通过将一段代码定义成函数,并为改函数指定一个函数名。这样即可在需要的时候多次调用这段代码。

    定义函数和调用函数

    定义函数的语法格式如下

    fun 函数名(形参列表)[:返回值类型]{
    //可执行语句
    }
    
    • 声明函数必须使用fun关键字
    • 函数名第一个单词首字母小写后面的每个单词首字母大写
    • 返回值类型可以是kotlin语言所允许的任何数据类型。如果声明了返回值类型,则函数体内应该有一条return语句,如果没有返回值类型,有两种声明方式:
      • 省略:返回值类型的部分
      • 使用:Unit指定返回Unit代表没有返回值。Kotlin的Unit就相当于Java的void
    • 形参列表:形参列表用于定义该函数可以接受的参数,形参列表由零组到多组“形参名:参数类型”组合而成,多组参数之间以英文逗号,隔开,形参名和形参类型之间以英文冒号:隔开。一旦指定了形参列表,在调用该函数时就必须传入对应的参数值
    
    fun max(x: Int, y: Int): Int {
        val z = if (x > y) x else y
        return z
    }
    
    fun sayHi(name: String): String {
        return "${name},您好"
    }
    

    函数返回值和unit

    如果希望明确指定函数没有返回值,有如下两种方式

    • 直接省略":返回值类型"的部分
    • 使用“:Unit”声明代表没有返回值
    fun callMax():Unit {
        val a = 1
        val b = 2
        println("最大值为${max(a, b)}")
    }
    
    

    递归函数

    在一个函数体内调用它自身,这种函数被称为递归函数
    例如有一个数学题:f(0)=1,f(1)=4,f(n+2)=2f(n+1)+f(n)可以使用递归来解决

    fun fn(n: Int): Int {
        if (n ==0) {
            return 1;
        }
        else if (n ==1) {
            return 4;
        }else{
            return 2* fn(n-1)+ fn(n-2)
        }
    }
    

    注意 递归一定要想已知方向进行,避免造成无穷递归

    单表达式函数

    在某些情况下,函数只是返回单个表达式,此时可以省略花括号并在等号后指定函数体

    fun area(x:Double,y:Double):Double= x*y
    
    

    函数的形参

    命名参数

    Kotlin函数除第一个参数之外,其他所有形参部分都分配隐式的外部形参名——这些外部形参名与内部形参名保持一直

    fun main(args: Array<String>) {
        //传统调用函数的方式
        girth(2.0, 4.0)
        //使用形参名传入参数
        girth(width = 2.1, height = 1.1)
        //使用命名参数可指定位置
        girth(height = 1.1, width = 2.5)
        //可使用部分形参名
        girth(1.1, height = 5.0)
    }
    
    
    fun girth(width: Double, height: Double): Double {
        return 2*(width+height)
    }
    

    如果希望调用函数时混合使用命名参数和位置参数,那么命名参数必须位于位置参数之后

    形参默认值

    在某些情况下,程序需要在定义函数时为一个或多个形参指定默认值

    形参名:形参类型=默认值
    

    形参的默认值紧跟在形参类型之后,中间以英文等号隔开

    fun main(args: Array<String>) {
        //全部使用默认参数
        sayHi()
        //使用一个默认参数
        sayHi("猪八戒")
        //全部不使用默认参数
        sayHi("沙和尚","西天取经")
    
        sayHi(message = "就是玩")
    }
    
    fun sayHi(name:String="孙悟空",message:String="啥也不是"){
        println("${name}${message}")
    }
    

    通过为函数形参指定默认值,可以减少函数重载的数量

    尾递归函数

    Kotlin还支持一种尾递归函数的编码方式,当函数将调用自身作为它执行的最后一行代码,且递归调用后没有更多的代码时,可使用尾递归语法。

    • 尾递归不能在异常处理的try、catch、finally块中使用
    • 尾递归函数需要使用tailrec修饰
    fun tailrecfun(n:Int):Int{
        //计算阶乘
        if (n==1) {
            return n
        }else {
            return n* tailrecfun(n-1)
        }
    }
    //尾递归写法
    tailrec fun tailRec(n:Int):Int = if(n==1) n else n*tailRec(n-1)
    

    个数可变形参

    在定义函数时,在形参名称前添加vararg修饰,则表明该形参可以接受多个参数值,多个参数值被当成数组传入

    fun multiParam(a:Int,vararg books:String){
        //books会被当作数组处理
        for (book in books) {
            println(book)
        }
        println(a)
    }
    

    kotlin允许个数可变的形参可以处于形参列表的任意位置(不要求是形参列表的最后一个参数),但是要求一个函数最多只能带一个个数可变的形参

    如果我们已经有一个数组,希望将数组传给可变形参,则可以在传入的数组参数前添加"*"运算符

      var arr = arrayOf("疯狂java讲义", "glide从入门到精通", "c++从入门到放弃")
        multiParam(11, *arr)
    

    函数重载

    与Java类似,Kotlin允许定义多个同名函数,只要形参列表不同或返回值类型不同。

    Kotlin的函数重载也只能通过形参列别区分,形参个数不同、形参类型不同都可以算重载。但仅有形参名不同、返回值类型不同、或修饰符不同,则不能算重载

    局部函数

    Kotlin支持在函数体内部i当以函数,称作局部函数
    局部函数对外是隐藏的,只能在其封闭函数内有效,封闭函数也可以返回局部函数。

    fun getMathFunc(type: String, nn: Int,mm:Int): Int {
        //定义局部函数
        fun add(a: Int, b: Int): Int {
            return a+b
        }
    
        fun minus(a: Int, b: Int): Int {
            return a - b
        }
    
        fun multi(a: Int, b: Int): Int {
            return a * b
        }
    
        fun divis(a: Int, b: Int): Int {
            return a / b
            
        }
        when (type) {
            "add" -> { return add(nn,mm)}
            "minus" -> { return minus(nn,mm)}
            "multi" -> { return multi(nn,mm)}
            "divis" -> { return divis(nn,mm)}
            else -> {
                return add(nn,mm)
            }
        }
    }
    

    高阶函数

    Kotlin不是纯粹的面向对象语言,Kotlin的函数也是一等公民,因此函数本身也具有自己的类型。就是函数类型,函数类型就像前面介绍的数据类型一样,可以用于定义变量,也可用作函数的形参类型,还可作为函数的返回值类型

    使用函数类型

    函数类型由函数的形参列表、->和返回值类型组成,例如

    func foo(a:Int,name:String) -> String{
    }
    

    该函数的形参列表、->和返回值类型为(Int,String)->String 这就说该函数的类型

    func bar(width:Double,height:Double){}
    

    该函数的形参列表、->和返回值类型为(Double,Double)->Unit或(Double,Double)这就是该函数的类型

    func test(){}
    

    该函数的形参列表、->和返回值类型为()->Unit或()

    我们可以这样定义一个函数类型的变量

    var myfun:(Int:Int)->Int
    

    定义了函数类型的变量后,接下来就可以对函数变量赋值

    当直接访问一个函数的引用,而不是调用函数时,需要在函数名前添加两个冒号::

    fun main(args: Array<String>) {
    //    multiParam(12,"疯狂java讲义","glide从入门到精通","c++从入门到放弃")
    val myfun:(Int,Int)->Int
        //将pow函数的引用赋值给函数类型myfun变量
        myfun = ::pow
        println(myfun(2, 3))
    }
    fun pow(base: Int, exponext: Int): Int {
        return base*exponext
    }
    

    使用函数类型作为形参类型

    fun main(args: Array<String>) {
        var paramArr = arrayOf(1, 2, 3, 4, 5, 6, 7, 8)
        println(map(paramArr, ::addOne).contentToString())
    }
    
    /**
     * 将数组中的每个元素+1
     */
    fun map(arrs: Array<Int>, fn: (Int) -> (Int)): Array<Int> {
        //初始化数组
        var result = Array<Int>(arrs.size, { 0 })
        //遍历arrs中的元素,对每个元素执行fn()运算
        for (i in arrs.indices) {
            result[i] = fn(arrs[i])
        }
        return result
    }
    
    fun addOne(args:Int):Int{
        return args+1
    }
    

    使用函数类型作为返回值类型

    fun main(args: Array<String>) {
        //获取add函数
        var add = getMathFunc("add")
        println(add(1, 1))
    }
    
    fun getMathFunc(type: String): (Int,Int)->Int {
        //定义局部函数
        fun add(a: Int, b: Int): Int {
            return a+b
        }
    
        fun minus(a: Int, b: Int): Int {
            return a - b
        }
    
        fun multi(a: Int, b: Int): Int {
            return a * b
        }
    
        fun divis(a: Int, b: Int): Int {
            return a / b
    
        }
        when (type) {
            "add" -> { return ::add}
            "minus" -> { return ::minus}
            "multi" -> { return ::multi}
            "divis" -> { return ::divis}
            else -> {
                return ::add
            }
        }
    }
    

    局部函数与Lambda表达式

    Lambda表达式时现代编程语言中引入的一种语法,Lambda更加灵活

    Lambda的语法

    {(形参列表)->
    //零到多条可执行语句
    }
    

    定义Lambda表达式有如下几点:

    • Lambda表达式总是被大括号括着
    • 定义Lambda表达式不需要fun关键字,无须指定函数名
    • 形参列表在->之前声明,参数类型可以省略
    • Lambda表达式的执行体放在->之后
    • 函数的最后一个表达式自动被作为Lambda表达式的返回值,无须使用return关键字
      上面的局部函数 我们可以改造为
    fun main(args: Array<String>) {
        var mathFunc = getMathFunc("divis")
        println(mathFunc(32, 4))
    
    }
    
    fun getMathFunc(type: String): (Int, Int) -> Int {
    
        when (type) {
            "add" -> {
                return { a: Int, b: Int -> a + b }
            }
            "minus" -> {
                return { c: Int, d: Int -> c - d }
            }
            "multi" -> {
                return { e: Int, f: Int -> e * f }
            }
            "divis" -> {
                return { h: Int, i: Int -> h / i }
            }
            else -> {
                return { a: Int, b: Int -> a + b }
            }
        }
    }
    

    调用Lambda表达式

    Lambda表达式的本质是功能更灵活的代码块,因此完全可以将Lambda表达式赋值给变量或直接调用Lambda表达式

    fun callLambdaExpression(){
        //定义lambda表达式,并赋值给square变量
        var square = {n:Int -> n*n}
        //调用Lambda表达式
        println(square(100))
        //定义第二个lambda表达式
        var result = {base:Int,exponent:Int ->
            base*exponent
        }(3,4)
        println(result)
    }
    

    上面程序中的第二个表达式没有赋值给任何变量,也没有将Lambda表达式传给任何函数或方法,因此程序只能在定义该表达式的同时调用它。程序在第二个Lambda表达式的后面使用圆括号执行调用,并传入相应参数

    利用上下文推断类型

    完整的Lambda表达式需要定义形参类型,如果Kotlin可以根据Lambda表达式上下文推断出形参类型,那么Lambda表达式就可以省略形参类型
    简化上面的程序

        //定义lambda表达式,并赋值给square变量 square指定类型
        var square:(Int)->Int = {n-> n*n}
        //调用Lambda表达式
        println(square(100))
    

    省略形参名

    Lambda表达式不仅可以省略形参类型,而且如果只有一个形参,那么Kotlin允许省略Lambda表达式的形参名,如果没有了形参名,那么->也不需要了,Lambda中可以通过it来代表形参

        var square:(Int)->Int = {it*it}
    
    

    上面it代表lambda表达式的形参,由于该lambda表达式只有一个形参 所以形参名可以省略 用it代替

    调用Lambda表达式的约定

    Kotlin语言有一个约定:如果函数的最后一个参数是函数类型,而且打算传入一个Lambda表达式作为相应的参数,那么就允许在圆括号之外指定Lambda表达式

        var list = listOf<String>("Java", "Kotlin", "Go")
        println(list.dropWhile(){ it.length > 3 })
    

    个数可变的参数和Lambda参数

    Kotlin约定:如果调用函数时最后一个参数是Lambda表达式,则可将Lambda表达式放在圆括号的外面,如果一个函数既包含个数可变的形参,也包含函数类型的形参,那么就应该将函数类型的形参放在最后

    匿名函数

    Lambda有个缺陷就是不能指定返回值类型。如果Kotlin无法推断出Lambda表达式的返回值类型,就需要显式指定返回值类型,而匿名函数即可代替Lambda表达式

    匿名函数的用法

    fun anonymousFunction(){
        //创建匿名函数
        var test = fun(x: Int, y: Int): Int {
            return x+y
        }
        println(test(2, 3))
    }
    

    匿名函数与普通函数基本相似。将普通函数的函数名去掉就变成了匿名函数

    匿名函数和Lambda表达式的return

    匿名函数的本质依然是函数,因此匿名函数的return则用于返回该函数本身,Lambda表达式的return用于返回所在的函数

    捕获上下文中的变量和常量

    Lambda表达式或者匿名函数(局部函数、对象表达式)可以访问或修改其所在上下文中的变量和常量。这个过程称之为捕获。

    Lambda表达式或匿名函数都会持有一个其所捕获的变量的副本

    内联函数

    在调用Lambda表达式或函数的过程中,程序要将执行顺序转移到被调用表达式或函数所在的内存地址,当被调用表达式或函数执行完毕后,再返回到原函数执行的地方,从这里可以看出,函数调用会产生一定的时间和空间的开销。为了避免产生函数调用的过程,我们可以考虑直接把调用的表达式或函数代码”嵌入“到原来的执行流程中。这个通过内联函数来实现

    内联函数的使用

    使用内联函数,只要使用inline关键字修饰带函数形参的函数即可

    //创建函数 对形参数组中的每个元素 执行fn函数
    inline fun map(arr: Array<Int>, fn: (Int) -> Int):Array<Int> {
        var result = Array<Int>(arr.size,{0})
        for (i in arr.indices) {
            result[i] = fn(arr[i])
        }
        return result
    }
    

    使用inline编译时,会发现编译结果只产生一个class文件,不会生成其他额外的内部类的class文件,相当于编译器帮我们”复制、粘贴“了要调用的代码到执行的代码中

    内联函数的缺点

    内联函数的本质时将被调用的Lambda表达式或函数的代码复制、粘贴到原来的执行函数中,如果被调用的Lambda表达式或函数的代码量特别大,且该Lambda表达式或函数多次被调用,就会增加代码

    部分禁止内联

    使用inline修饰函数后,所有传入该函数的Lambda表达式或函数都会被内联化,那么可不可以部分内联呢?我们可以使用noinline来修饰
    noinline用于显式阻止某一个或某几个形参内联化

    非局部返回

    在Lambda表达式中使用return返回的不是Lambda表达式而是返回该表达式所在的函数。由于内联的Lambda表达式会被拼接到调用它的函数中,此时在Lambda中使用return就直接写在了Lambda表达式的调用函数中一样,因此,该内联的Lambda表达式中的return可以返回所在的函数 这种返回就称为非局部返回

    相关文章

      网友评论

        本文标题:Kotlin——函数和Lambda表达式

        本文链接:https://www.haomeiwen.com/subject/bklvrltx.html