Koltin - 函数与Lambda表达式

本文介绍了Kotlin中的函数特性,包括函数定义、形参列表、返回值、局部函数、高阶函数、Lambda表达式及其优势。还讨论了匿名函数、函数类型的推断、捕获上下文变量以及内联函数的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Koltin对Java的纯粹面向对象进行了弥补,增加了函数式编程的支持。即Kotlin融合了面向过程和面向对象语言的特征,因此Kotlin完全支持定义函数和调用函数。

Kotlin的函数比C语言的函数更强大,Kotlin支持局部函数(实际上局部函数是Lambda表达式的基础)。

==> 函数是Kotlin中非常重要的一个知识点!!!

2、fun 函数名(形参列表)[:返回值类型]{

//零到多条可执行的语句

}

形参列表由零到多组"形参名:参数类型"组成;

如果函数没有返回值,则可以省略:返回值类型或使用:Unit;

3、当程序调用一个函数时,既可以把调用函数的返回值赋值给变量,也可以将函数的返回值传给另一个函数,作为另一个函数的参数。

4、在某些情况下,函数只是返回单个表达式,此时可以省略花括号并在等号后指定函数体即可。这种方式被称为单表达式函数。

fun max(x:Int,y:Int):Int = if(x>y) x else y

对于单表达式函数而言,编译器可以推断出函数的返回值类型,因此Kotlin允许省略声明函数的返回值类型。

5、函数的形参

  • 命名参数

Kotlin允许调用函数通过名字来传入参数,因此Kotlin函数的参数名应该具有更好的语义 - 程序可以立刻明确传入函数的每个参数的含义。

fun girth(width:Double,height:Double):Double{

。。。。。

}

调用:girth(1.1,1.2)或girth(width = 1.1,height = 1.2)或girth(height = 1.2,width = 1.1)或girth(1.1,height = 1.2)

需要说明的是,如果希望调用函数时,混合使用命名参数和位置参数,那么命名参数必须位于位置参数之后。换句话说就是命名参数之后只能是命名参数。比如:

girth(width = 1.1, 1.2)//位置参数必须放在命名参数之前,因此这行代码会报错

  • 形参默认值【可以减少函数重载的数量】

语法:

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

示例:

fun sanHi(name:String = "孙悟空",message:String="哈哈哈"){

println(name + message)

}

调用函数,如果只传入一个位置参数,由于该参数位于第一位,系统会默认将该参数值传给name参数。

Kotlin建议将带默认值的参数定义在形参列表的最后。 

  • 尾递归函数[tailrec修饰函数]

当函数将调用自身作为它执行的最后一行代码,且递归调用后,没有更多的代码时,可使用递归语法。

比如:

 fun fact(n:Int):Int{

if(n==1)

return 1

else

return n*fact(n-1)

}

相当于

tailrec fun fact(n:Int,total:Int=1):Int = if (n==1) total else fact(n-1,total*n)

与普通的递归相比,编译器会对尾递归进行修改,将其优化为一个快速而高效的基于循环的版本,这样就可以减少可能对内存的消耗。

  • 个数可变的形参[vararg修饰]

fun test(a:Int,vararg books:String){}

参数个数可变的形参本质是一个数组参数;

kotlin要求一个函数最多只能带一个个数可变的形参;

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

var book = arrayOf("java","kotlin","android")

test(3,*book)

  • 函数重载:kotlin函数重载只能通过形参列表进行区分,形参的个数或类型不同都可以算函数重载。

6、局部函数

放在函数体内部的函数,称为局部函数。局部函数对外部是隐藏的,局部函数只能在其封闭函数内有效,其封闭函数也可以返回局部函数,以便程序在其他作用域使用局部函数。

fun outterFun(type:String,nn:Int):Int{

fun square(n:Int):Int{

return n*n

}

fun cube(n:Int):Int{

return n*n*n

}

when(type){

"square"->return square(nn)

"cube" -> return cube(njn)

}}

如上程序所示,封闭函数并没有将局部函数返回,那么局部函数只能在封闭函数内部调用。

如果封闭函数将局部函数返回,且程序使用变量保存了封闭函数的返回值,则这些局部函数的作用域将扩大,因此程序完全可以自由地调用它们,就像它们是全局函数一样。

7、高阶函数

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

  • 函数类型

(形参列表)-> 返回值类型

例如:

fun test(m:Int,n:String):String{}

其函数类型就是(Int,String)->String 

示例:定义变量

var fun:(Int,Int)->Int

fun pow(base:Int,exponent:Int):Int{

。。。}

fun = ::pow//当直接访问函数的引用,而不是调用函数的时候,需要在函数名前加冒号,而不能在函数名后加括号,一旦添加了括号,就变成函数调用了。

示例:作为形参类型

fun map(data:Array<Int>,fn:(Int,Int)->Int):Array<Int>{

.....

}

如果在定义函数的时候,该函数的大部分代码的逻辑是确定的,但是某些逻辑的处理是不确定的,这意味着某些代码是需要动态改变的,如果希望调用函数时能动态的传入这些代码,就需要在函数中定义函数类型的形参。如上所示。

map(arr,::square)

示例:作为返回值

fun getMathFun(type:String):(Int)->Int{

fun square(n:Int):Int{

return n*n

}

fun cube(n:Int):Int{

return n*n*n

}

when(type){

"square"->return ::square

"cube" -> return ::cube

}}

调用:var mathSquare = getMathFun("square")

8、Lambda表达式(本质是功能更加灵活的代码块)

如果说函数是命名的、方便复用的代码块,那么Lambda表达式则是功能更加灵活的代码块,它可以在程序中被传递和调用。

由上一节:

when(type){

"square"->return ::square

"cube" -> return ::cube

}

可以看到,作为返回值,局部函数的函数名一旦离开封闭函数就失去了意义。既然局部函数的函数名没有太大的意义,就可以考虑使用Lambda表达式来简化局部函数的写法。

when(type){

"square"->return {n:Int->n*n}

"cube" -> return  {n:Int->n*n*n}

}

语法:

{形参列表->

//零到多条可执行语句

}

与局部函数的区别:

  • Lambda表达式总是被大括号括着

  • 不需要fun关键字,无需指定函数名

  • 形参列表(如果有的话)在->之前声明,参数类型可以省略

  • 函数体(Lambda表达式执行体)放在->之后

  • 函数的最后一个表达式自动被作为Lambda表达式的返回值,无需使用return关键字

8.1、Lambda表达式可以赋值给变量或者直接调用

var square = {n:Int->n*n}

square(2)

{n:Int->n*n}(2)//自调用

8.2、Lambda表达式的类型推断

完整的Lambda表达式需要定义形参类型,但是如果Kotlin可以根据Lambda表达式的上下文推断出形参类型,那么Lambda表达式就可以省略形参类型。(其实和普通变量的的类型推断没什么区别)

比如:

var square:(Int)->Int={n->n*n}//可推断,因此可省略形参类型

var square = {n->n*n}//错误,因为不可推断,所以必须加上形参类型,即{n:Int->n*n}

8.3、省略形参名

如果只有一个形参,Kotlin允许省略Lambda表达式的形参名。如果Lambda表达式省略了形参名,此时->也不需要了,Lambda表达式可通过it来代表形参。

比如:

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

8.4、调用Lambda表达式的约定

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

示例:

var rt = list.dropWhile(){it.length>3}

当然,如果Lambda表达式是函数调用的唯一参数,那么调用放大的圆括号也是可以省略的呀

var rt = list.dropWhile{it.length>3}

==>建议将函数类型的形参放在参数列表的最后,这样方便以后传入Lambda表达式

9、匿名函数

Lambda表达式有一个缺陷就是不能指定返回值的类型。由于Kotlin可以推断出Lambda表达式的返回值类型,因此即使不为Lambda表达式指定返回值类型也是没问题的。但在一些特殊的场景下,Kotlin无法推断出Lambda表达式的返回值类型,此时就需要显式指定返回值类型,或者匿名函数即可替代Lambda表达式。

var test = fun(x:Int,y:Int):Int{

}

9.1、匿名函数与类型推断

a、如果系统可以推断匿名函数的形参类型,那么匿名函数允许省略形参类型;

listOf(3,5,6,7).filter(

fun(e1):Boolean{....}

)

filter 方法的类型是:(Int)->Boolean,因此系统可推断该参数类型必是(Int)->Boolean,因此允许省略形参类型

b、返回值类型的声明规则:

如果使用普通代码块作为函数体,则必须显式的指定返回值类型,否则会被认为没有返回值;

如果使用单表达式作为函数体,则无需指定返回值类型,系统可以自动的推断。

var wawa = fun(x:Int,y:Int) = x*y

9.2、匿名函数和Lambda表达式的return

匿名函数的return作用于函数本身;

Lambda表达式的return作用域所在的函数(一般很少使用);

比如:

list.forEach(fun(n){

。。

return

})

list.forEach({n:Int->

。。。

return

})

如果上面两个调用均在main函数中,则第二种方式的调用只能遍历一个集合元素。

10、捕获上下文中的变量和常量(内部函数可访问外部函数定义的变量和常量)

Lambda表达式或匿名函数(以及局部函数、对象表达式)可以访问或修改(俗称“闭包”)其所在的上下文中的变量和常量。

即使定义这些变量和常量的作用域已经不存在了,Lambda表达式或匿名函数依然可以访问和修改它们。Why?

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

11、内联函数(以代码量的增加为代价,换取性能的提升)

背景:调用Lambda表达式或函数的过程是:程序要将执行顺序转移到被调用表达式或函数所在的内存地址,当被调用表达式或函数执行完之后,再返回到原函数执行的地方。

在这个转移的过程中系统要做如下事情:

  • 为被调用的表达式或函数创建一个对象

  • 为被调用的表达式或函数所捕获的变量创建一个副本

  • 在跳转到被调用的表达式或函数所在的地址前,要保护现场并记录执行地址,以便调用结束之后的现场恢复,并按原来保存的地址继续,也就是通常所说的压栈和出栈。

可以看出,函数调用会产生一定的时间和空间开销,如果被调用的表达式和函数的代码量本身不大,而且会被经常的调用,那么这个时间和空间的开销就很不划算了。

因此,直接将被调用的表达式或函数的代码“嵌入”原来的执行流中 - 简单的说就是编译器负责去“复制、粘贴”:复制被调用的表达式或函数的代码,然后粘贴到原来的执行代码中。为了让编译器帮我们干这件事,可通过内联(inline)函数来实现。

=》如果被调用的表达式或函数包含大量的执行代码,则不应该使用内敛函数。因为每调用一次,就会复制一次,势必会造成代码激增。

noinline=》禁止内联

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值