文章目录
- 函数的定义
- 函数的返回值
- [参数默认值 & 调用时参数指定](#参数默认值 & 调用时参数指定)
- 函数作用域
- [Lambda 表达式](#Lambda 表达式)
- 匿名函数
- 内联函数
- 扩展函数
- 中缀函数
- [递归函数 & 尾递归函数](#递归函数 & 尾递归函数)
函数的定义
函数可以理解成一个小小的加工厂,给入特定的原材料,它就会给出特定的产品。
fun [接收者类型.][函数名]([参数名: 参数类型], ...)[:返回值类型] [函数体]
我们只需要写一次,便可以反复使用:
kt
fun greeting(name: String) {
println("Hello $name!")
}
fun main() {
greeting("Today")
greeting("Kotlin")
}
Hello Today
Hello Kotlin
Note:调用函数时,函数参数有多个,且未指定参数名,需要按参数顺序传入参数。
函数的返回值
函数都会有返回值,默认的返回值是Unit
(无论何时,Unit
都是唯一的一个值(单例),不可以被实例化),可以省略不写:
kt
fun greeting(name: String): Unit {
println("Hello $name!")
return Unit
// return
}
函数的返回值会给到调用处,也就是说下方的getResult()
就代表了默认返回值Unit
。我们可以直接打印出返回值,或者将其赋值给变量:
kt
fun getResult() {}
fun main() {
val result: Unit = getResult()
println(result)
print(getResult())
}
kotlin.Unit
kotlin.Unit
我们也可以指定返回其他类型:
kt
fun plus1(old: Int): Int {
return old + 1
}
fun main() {
val new = plus1(0)
print(new)
}
1
甚至可以返回一个函数(这可能有点超纲了,看不懂可以先不理,请参考下方Lambda 表达式 和匿名函数 ):
这里的getFun
返回了一个无参数的、返回值为String
类型的函数。
kt
fun getFun(): () -> String {
return { "Hello World" }
}
fun main() {
val greet = getFun()
val msg = greet()
print(msg)
}
Hello World
Note:Kotlin 中函数类型的书写格式是(参数名和冒号
:
可省略):([参数名: 参数类型], ...) -> [返回值类型]
如
ktfun main() { val myFun: (Int) -> Int = { it + 1 } print(myFun(0)) }
1
与之类似的写法是:
ktfun main() { fun myFun(int: Int): Int { return int + 1 } print(myFun(0)) }
或者
kotlinfun main() { val myFun = fun(int: Int): Int { return int + 1 } print(myFun(0)) }
一个函数可以作为另一个函数的参数:
kt
fun caller(block: () -> Unit) {
// 调用 block 函数
block()
}
fun main() {
// 调用 caller 函数,传入 lambda
caller({ print("Hello World") })
}
Hello World
如果函数体可以用一句表达式表示,则可以直接用等号=
,返回值类型也可以省略:
kt
fun getYear() = 2024
fun getInt(boolean: Boolean) = if (boolean) 1 else 0
fun getBoolean(int: Int) = when(int) {
0 -> false
else -> true
}
fun main() {
println(getYear())
println(getInt(true))
print(getBoolean(1))
}
2024
1
true
参数默认值 & 调用时参数指定
当某个参数为可选时,可以为参数赋默认值(我们一般将可选参数放于必要参数之后,但这不是硬性要求):
kt
fun greet(otherParams: Int, name: String = "Kotlin") {
print("Hello $name")
}
fun main() {
greet(0)
}
Hello Kotlin
我们也可以在调用时指定参数传值(下方的exampleParam
):
kt
fun greet(name: String, exampleParam: Int = 0, exampleParam1: Int = 1) {
println("Hello $name")
print("exampleParam: $exampleParam, exampleParam1: $exampleParam1")
}
fun main() {
greet("Kotlin", exampleParam = 2)
}
Hello Kotlin
exampleParam: 2, exampleParam1: 1
函数作用域
在函数中定义的变量、函数,在函数外无法访问:
kt
fun main() {
fun funScope() {
val name = "Kotlin"
fun innerFun() {}
}
// print(name) 无法访问
// innerFun() 无法访问
}
函数内可以访问函数外定义的变量、函数:
kt
fun main() {
val name = "Kotlin"
fun outerFun() { print("Outer Fun") }
fun funScope() {
// 可以调用外部内容
println(name)
outerFun()
}
funScope() // 我们需要调用函数才能看到其运行结果
}
Kotlin
Outer Fun
Lambda 表达式
我们已经不止一次提到它了,它的写法是这样的:
// 匿名函数
{ [参数名: 参数类型], ... ->
[函数体]
}
不指明类型的函数类型变量,它的类型会是() -> Unit
。
当 lambda 的参数只有一个时,这个参数可以省略不写,通过it
指定:
kt
fun main() {
val myLambda: (String) -> Unit = {
print("Hello $it")
}
}
当 lambda 作为函数的最后一个参数进行传递时,可以将花括号{}
移到调用的小括号()
外面,称为尾部 lambda(trailing lambda):
kt
fun caller(name: String, block: () -> Unit) {}
fun main() {
caller("Kotlin", { print("不移出小括号") })
caller("Kotlin") {
print("移出小括号")
}
}
若没有return
,lambda 表达式的最后一个值会作为函数的返回值:
kt
fun caller(block: () -> String) {
print(block())
}
fun main() {
caller { "ABC" }
}
ABC
匿名函数
匿名函数无需写函数名(lambda 表达式也是匿名函数):
fun([参数名: 参数类型], ...)[: 返回值类型] [函数体]
kt
fun main() {
val mySingleLineFun = fun() = 1
val myFun = fun(name: String) {
print("Hello $name")
}
println(mySingleLineFun())
myFun("Kotlin")
}
1
Hello Kotlin
内联函数
内联函数可以将参数中 lambda 表达式的代码插入到函数调用处,提高性能。声明内联函数只需要在fun
前加inline
。内联函数会使编译后的代码更加庞大,我们必须在最合适的时候使用它(错误使用时 IDEA 会警告)。用得比较多的场景是函数有参数为函数类型。
kt
inline fun caller(block: () -> String) {
print(block())
}
fun main() {
caller { "ABC" }
}
print
或println
因为参数类型为Any
,可能为函数类型。可以看到 JVM 平台它们的实现(actual
)其实是 Java 的System.out.print
或System.out.println
(可以通过按住Ctrl
键,鼠标点击函数,跳转到函数声明处):
kt
//
@kotlin.internal.InlineOnly
public actual inline fun print(message: Any?) {
System.out.print(message)
}
...
@kotlin.internal.InlineOnly
public actual inline fun println(message: Any?) {
System.out.println(message)
}
如果不希望某一函数类型的参数被内联时,可以将其标记为noinline
:
kt
inline fun caller(
noinline noinlineBlock: () -> Unit
) {}
当内联函数(下方call
)可内联的函数类型参数(下方block
)被传入的非 内联函数(下方noinlineBlock
)调用时,需要标记为crossinline
:
kt
inline fun caller(
noinline noinlineBlock: () -> Unit
) {}
inline fun call(crossinline block: () -> Unit) {
caller{ block() }
}
扩展函数
还记得我们最开始说的函数类型声明吗?这里有一个接收者:
fun [接收者类型.][函数名]([参数名: 参数类型], ...)[:返回值类型] [函数体]
我们可以通过声明接收者,将某一函数定义为接收者所拥有的函数,称为其扩展函数。
这可能有点难以理解,因为我们还没有讲到类,我在这里做一个简单的解释:
- 我们定义了一个以
Int
作为接收者的函数add
用于对Int
类型数值加上(+
)值other
。 - 在该函数中,
this
会指代接收者Int
类型,例如这里调用int.add
,在add
中this + other
相当于0 + 3
,结果为3
,会返回到调用处。
kt
fun Int.add(other: Int) = this + other
fun main() {
val int = 0
print(int.add(3))
}
3
中缀函数
我们可以通过 infix
关键字将一个函数声明为中缀函数,我们还是以上方扩展函数为例(因为中缀函数必须为扩展函数 或成员方法 ,而且有且仅有一个 参数)。
可以看到运行结果其实是一样的,只是在调用时可以将int.add(3)
写成int add 3
,函数名作为类似运算符的存在。
kt
infix fun Int.add(other: Int) = this + other
fun main() {
val int = 0
println(int.add(3))
print(int add 3)
}
3
3
递归函数 & 尾递归函数
递归就是一个函数自己调用自己。
kt
fun myFun() {
myFun()
}
我们细想一下就会发现这样是不可取的,myFun
调用了myFun
,而被调用的myFun
又调了myFun
······看来一时半会是停不了了。
如果我们给递归加上条件,当start == end
时才递归,它就能够停下来:
kt
fun countTo(end: Int, start: Int = 0) {
println("现在是: $start")
if (start == end) return
else countTo(end, start + 1)
}
fun main() {
countTo(5)
}
现在是: 0
现在是: 1
现在是: 2
现在是: 3
现在是: 4
现在是: 5
其实就有点像循环:
kt
fun main() {
var start = 0
val end = 5
while (start <= end) {
println("现在是: $start")
start ++
}
}
当递归调用在末尾时,可以在fun
前加tailrec
,使函数成为尾递归函数(tail recursive functions),编译器会优化该递归,生成一个循环(参考示例)。