4、函数

函数

fun main() {
		println("Hello World")
}

我们程序的入口点就是main函数,我们只需要将我们的程序代码编写到主函数中就可以运行了,不过这个函数只是由我们来定义,而不是我们自己来调用。当然,除了主函数之外,我们一直在使用的println也是一个函数,不过这个函数是标准库中已经实现好了的

声明函数

fun 函数名称([函数参数...]): 返回值类型 {
    //函数体
}
fun main(args: Array<String>) {
    sayHello1()
    sayHello2()
    println(add(10,20))
}

fun sayHello1(){
    println("Hello")
}
fun sayHello2(): Unit{
    println("Hello")
}
fun add(num1: Int,num2: Int): Int{
    return num1 + num2
}

参数

对于上面的函数addnum1num2就是函数的参数,Kotlin 支持参数声明默认值

fun main(args: Array<String>) {
    println(add(10,20)) // 30
    println(add(10)) // 20 , 10传给了第一个参数
    println(add()) // 11 , 两个都是用默认参数
    println(add(num2 = 20)) // 21 , 将 20 指定传给第二个参数
}

fun add(num1: Int = 1,num2: Int = 10): Int{
    return num1 + num2
}

可变长参数,使用vararg关键字将参数标记为可变长参数

注意:可变长参数在函数的形参列表里面只能存在一个

fun main() {
    test("zhangsan","lisi","wangwu")
}

fun test(vararg names: String){
    for (name in names){
        println(name)
    }
}

可变长参数的本质是一个数组,但是不同于 Java,可变长参数不可以直接传入一个数组,而需要使用*来传入

fun main() {
    var names = arrayOf("zhangsan","lisi","wangwu")
    test("xiaoming",*names,"zhaoliu") // 使用*传入数组,并且前后都可以续写
}

fun test(vararg names: String){
    for (name in names){
        println(name)
    }
}

返回值

对于上面的函数sayHello实际返回是Unit类型,这个类型表示空,类似于Java中的void,默认情况下可以省略

返回值简化,对于一些内容比较简单的函数,比如上面仅仅是计算两个参数的和,我们可以直接省略掉花括号

fun main(args: Array<String>) {
    println(add(10,20)) // 30
    println(sub(20,10)) // 10	
}

fun add(num1: Int,num2: Int): Int = num1 + num2

fun sub(num1: Int,num2: Int) = num1 - num2 // 自动推断返回值类型

嵌套函数

函数内部也可以定义函数

fun main() {
    myFun1()
}

fun myFun1(){
    val a = 1
    fun myFun2(){
        println(a)
    }
    myFun2()
}

函数内的函数作用域是受限的,我们只能在函数内部使用;内部函数可以访问外部函数中的变量。

重载

我们不能同时编写多个同名函数,但是如果多个同名函数的参数不一致,是允许的。

注意:返回值类型不同不能作为重载条件!

fun add(num1: Int,num2: Int): Int{
    return num1 + num2
}

fun add(num1: Int,num2: Int,num3: Int): Int{
    return num1 + num2 + num3
}

递归函数

在Kotlin中,可以使用tailrec修饰符来标记尾递归函数。编译器会识别被tailrec修饰的函数,并进行优化处理,将递归调用转换为循环,从而避免栈溢出问题。

tailrec fun test(n: Int, sum: Int = 0): Int {
    if(n <= 0) return sum   //到底时返回累加的结果
    return test(n - 1, sum + n)  //不断累加
}

高阶函数与lambda表达式

Kotlin中的函数属于一等公民,它支持很多高级特性,甚至可以被存储在变量中,可以作为参数传递给其他高阶函数并从中返回,就想使用普通变量一样。 为了实现这一特性,Kotlin作为一种静态类型的编程语言,使用了一系列函数类型来表示函数,并提供了一套特殊的语言结构,例如lambda表达式。

声明规则

所有函数类型都有一个括号,并在括号中填写参数类型列表和一个返回类型,比如:(A, B) -> C 表示一个函数类型,该类型表示接受类型AB的两个参数并返回类型C的值的函数。参数类型列表可为空的,比如() -> A,注意,即使是Unit返回类型也不能省略。

作为变量声明

var add: (Int,Int) -> Int

此时add为变量名,(Int,Int) -> Int为变量类型

函数变量的赋值

// 使用 Lambda 表达式赋值
var add: (Int,Int) -> Int = { num1, num2 -> num1 + num2}
// 使用匿名函数赋值
var myFunc: (String) -> Unit = fun (str: String){
    println(str)
}

赋值已存在的函数

fun test(str: String){
    println("hello")
}

var myFunc: (String) -> Unit = ::test

作为函数参数声明

fun main() {
    test { str -> print(str) }
}

fun test(handle: (String) -> Unit){
    handle("Hello")
}

类型别名

如果一个方法中声明了多个函数类型的参数,或者是存在函数类型参数嵌套,那么可以使用类型别名来缩短名称

fun main() {
    test { str -> print(str) }
}

typealias HandleFun = (String) -> Unit

fun test(handle: HandleFun){
    handle("Hello")
}

Lambda

一个Lambda表达式只需要直接在花括号中编写函数体即可{params -> returnValue}

除了上面的可以使用 Lambda 表达式给一个函数类型变量赋值以外,还可以使用 Lambda 表达式传参

fun main() {
    test({msg -> println(msg)})
}

fun test(handle: (String) -> Unit){
    handle("Hello")
}

如果函数的最后一个形式参数是一个函数类型,可以直接写在括号后面,就像下面这样

test(){msg -> println(msg)}

如果函数只有一个参数,那么可以省略小括号

test{msg -> println(msg)}

这种语法也被称为尾随lambda表达式,能省的东西都省了,不过只有在最后一个参数是函数类型的情况下才可以,如果不是最后一位,就没办法做到尾随了。

参数

单个参数

var myFunc: (String) -> String = {
    println("Hello, $it") // 如果只有一个参数,则使用it来获取
    "Hello, $it" // 最后一行作为返回值
}

多个参数

var add :(Int,Int) -> Int = { x,y ->  // 我们需要手动添加两个参数这里的形参名称,不然没法用他两
    x + y  // 最后一行作为返回值
}

返回值

Lambda中没有办法直接使用return语句返回结果,而是默认使用最后一行的值作为返回值。

但是有的时候,我们需要在函数中提前返回,这就需要用到之前我们学习流程控制时用到的标签。

var myFunc: (Boolean) -> String = test@{ b ->
    if (b){
        return@test "True" // 使用标签进行返回
    }
    "False" // 最后一行作为返回值
}

如果是函数调用的尾随lambda表达式,默认的标签名字就是函数的名字

内联函数

使用高阶函数会可能会影响运行时的性能:每个函数都是一个对象,而且函数内可以访问一些局部变量,但是这可能会在内存分配(用于函数对象和类)和虚拟调用时造成额外开销。

为了优化性能,开销可以通过内联Lambda表达式来消除。使用inline关键字会影响函数本身和传递给它的lambdas,它能够让方法的调用在编译时,直接替换为方法的执行代码。

fun main() {
    test()
}

inline fun test(){
    println("Hello")
    println("Hello")
    println("Hello")
}

由于test函数是内联函数,在编译之后,会原封不动地把代码搬过去

fun main() {
    println("Hello")
    println("Hello")
    println("Hello")
}

同样的,如果是一个高阶函数,效果那就更好了,例如如下函数

fun main() {
    test { println(it) }
}

inline fun test(handle: (String) -> Unit){
    println("test")
    handle("Hello")
}

由于test函数是内联的高阶函数,在编译之后,不仅会原封不动地把代码搬过去,还会自动将传入的函数参数贴到调用的位置

fun main(){
  println("test")
  var it = "Hello"
  println(it)
}