函数是执行特定任务的一段代码。程序通过将一段代码定义成函数,并为改函数指定一个函数名。这样即可在需要的时候多次调用这段代码。
定义函数和调用函数
定义函数的语法格式如下
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) {
//传统调用函数的方式
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) {
//全部使用默认参数
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) {
// 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) {
var paramArr = arrayOf(1, 2, 3, 4, 5, 6, 7, 8)
println(map(paramArr, ::addOne).contentToString())
}
/**
* 将数组中的每个元素+1
*/
fun map(arrs: Array, fn: (Int) -> (Int)): Array {
//初始化数组
var result = Array(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) {
//获取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) {
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("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, fn: (Int) -> Int):Array {
var result = Array(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可以返回所在的函数 这种返回就称为非局部返回