Kotlin-函数

本文详细介绍了Kotlin中的函数,包括函数声明、参数(默认参数、命名参数、不定数量参数)、返回值(Unit、单表达式函数、明确指定返回值类型)、函数调用(传统用法、中缀标记法)、函数范围(局部函数、成员函数、扩展函数)、尾递归函数、高阶函数(函数类型、Lambda表达式与匿名函数)等概念,深入探讨了Kotlin函数的多样性和灵活性。

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

概述

许多程序设计语言(像C和C++)用函数这个术语来描述命名子程序;在Kotlin中,依然延续这个术语来表示“做某些事情的方式”,实际上把它当作java中的方法是一样的,只不过是命名方式的不同而已,没有什么实际意义。

函数声明

Kotlin中的函数决定了一个对象能够接收到什么样的信息,函数的基本组成部分包括:名称、参数、返回值和函数体,其使用fun关键字定义函数,定义形式为:

fun methodName(param: paramType): ReturnType {

}
  • 返回类型描述的是在调用函数之后从函数返回的值。
  • 参数列表给出了要传递给函数的信息的类型和名称
  • 函数名和参数列表作为函数的唯一的标识符

参数

函数参数的定义使用Pascal标记法, 也就是, name: type 的格式,多个参数之间使用逗号分隔,每个参数都必须明确指定类型.

fun powerOf(number: Int, exponent: Int) {
...
}

默认参数

函数参数可以指定默认值, 当参数省略时, 就会使用默认值. 与其他语言相比, 这种功能使得我们可以减少大
量的重载(overload)函数定义.

fun main(args: Array<String>) {
    // 使用默认值
    doSwim() //打印: "do swim"

    // 使用传递的参数
    doSwim("just do it") // 打印:"just do it"
}

fun doSwim(sports: String = "do swim") {
    println(sports)
}

参数默认值的定义方法, 在参数类型之后, 添加 = 和默认值.

命名参数

调用函数时, 可以通过参数名来指定参数. 当函数参数很多, 或者存在默认参数时, 指定参数名是一种非常便利的功能.

有这样一个函数:

fun register(name: String, no: Int = 1001, sex: Int = 0) {
    println("name: $name, no:$no, sex: $sex")
}

当我们采用默认方式调用时,如果未设定的参数,位于参数列表的第一个,我们可以这样调用

register("li")

其实际上相当于

register("li", 1001, 0)

人如果我们不需要指定所有的参数, 只是修改部分默认的参数值,我们可以这样:

register(name = "wang", no = 1003)

不定数量参数

如果在函数被调用以前,函数的参数(通常是参数中的最后一个)个数不能够确定,可以采用不定量参数方式,定义函数参数列表。比如在创建List时,创建前并不知道预添加至List中多少数据。

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts 是一个 Array
        result.add(t)
    return result
}

调用时, 可以向这个函数传递不定数量的参数:

val list = asList(1, 2, 3)

在一个函数中只可以声明一个参数为vararg。与Java不同的是,在Kotlin中标记为vararg的参数不一定是最后一个。如果标记为vararg的参数不是最后一个,那么vararg参数之后的其他参数, 可以使用命名参数语法来传递参数值, 或者, 如果参数类型是函数, 可以在括号之外传递一个
Lambda表达式.

fun main(args: Array<String>) {

    specialty("苹果", "橘子", addr = "AAA")

}

fun specialty(vararg fruits: String, addr: String) {
    for (fruit in fruits) {
        println("fruit:$fruit, addr: $addr")
    }
}

// Log
fruit:苹果, addr: AAA
fruit:橘子, addr: AAA

返回值

Unit

如果一个函数不返回任何有意义的结果值,那么它的返回类型为Unit .Unit 类型只有唯一的一个值Unit,在函数中,不需要明确地返回这个值
对于返回值为Unit的函数,Unit可以省略。

fun register(name: String, no: Int = 1001, sex: Int = 0): Unit {
    println("name: $name, no:$no, sex: $sex")
}

上例中的代码等价于:

fun register(name: String, no: Int = 1001, sex: Int = 0) {
    println("name: $name, no:$no, sex: $sex")
}

单表达式函数

如果一个函数的函数体只有一个表达式,函数体可以直接写在 “=”之后,也就是这样:

fun double(x: Int): Int = x * 2

如果编译器可以推断出函数的返回值类型, 那么返回值的类型定义是可省略:

fun double(x: Int) = x * 2

明确指定返回值类型

如果一个函数体由多行语句组成的代码段,那么必须明确指定返回值类型,除非函数的的返回值为Unit。

函数的调用

传统用法

函数的调用使用传统的方式:

val result = double(2)

调用类的成员函数时, 使用点号标记法(dot notation):

Sample().foo() // 创建一个 Sample 类的实例, 然后调用这个实例的 foo 函数

中缀标记法(Infix notation)

使用中缀标记法(infix notation)来调用函数, 但函数需要满足以下条件:

  • 是成员函数, 或者是扩展函数
  • 只有单个参数
  • 使用 infix 关键字标记

    class Person(var name: String, var age: Int) {

    // 使用infix 关键字标记,该函数可被中缀标记法法调用
    infix fun printName(addr: String) {
        println("addr: $addr, name: $name")
    }
    

    }

    fun main(args: Array) {
    val person: Person = Person(“Jone”, 20)

    // 使用中缀标记法调用扩展函数
    person printName("AA-BB") // Log: addr: AA-BB, name: Jone
    
    // 上面的语句等价于
    person.printName("AA-BB") 
    

    }

函数的范围

在Kotlin中,函数不仅仅能够被定义为top_level,即包下的函数,还可以被定义为局部函数, 成员函数, 以及扩展函数。函数的定义方式不同,其作用域也不劲相同,当然了,函数的作用域还与修饰符相关,具体可参考 Kotlin-可见性修饰符.

局部函数

所谓的局部函数,就是定义在函数体的内涵数。

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: Set<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    } 

    dfs(graph.vertices[0], HashSet())
}

局部函数可以访问外部函数中的局部变量(也就是, 闭包), 因此, 在上面的例子中, visited 可以定义为一个局部变量。

成员函数

成员函数是指定义在类或对象之内的函数。

class Sample() {
    fun foo() { print("Foo") }
}

对成员函数的调用使用点号标记法。

Sample().foo() // 创建 Sample 类的实例, 并调用 foo 函数

扩展函数

可参考 Kotlin-扩展.

尾递归函数

Kotlin支持一种称为尾递归(tail recursion)的函数式编程方式.这种编程方式,到底是用来干什么的呢?这种方式是基于函数表达式和递归函数,来实现某些基本循环的的算法,采用这种方式可以有效的避免栈溢出的危险。

当函数被关键字tailrec修饰,同时满足尾递归(tail recursion)的函数式编程方式的形式时,编译器就会对代码进行优化, 消除函数的递归调用, 产生一段基于循环实现的, 快速而且高效的代码。

tailrec fun plus(start: Int, end: Int, result: Int): Int = if (start >= end) result else plus(start+1, end, start + result) 

// Test
fun main(args: Array<String>) {

    println(plus(0, 10, 0)) // 打印结果 45
}

上面的代码计算了从start到end之间的所有数的和,并将和与初始值相加后返回。编译器优化产生的代码等价于下面这种传统方式编写的代码:

fun plus(start: Int, end: Int, result: Int): Int {
    var res = result
    var sta = start

    while (sta < end) {
        res += sta
        sta++
    }

    return res
}


注:

  1. 要符合 tailrec 修饰符的要求, 函数必须在它执行的所有操作的最后一步, 递归调用它自身
  2. 如果递归调用后还有其他逻辑代码,不能使用尾递归
  3. 尾递归不能用在try/catch/finally 结构内
  4. 尾递归目前只能用在JVM环境内

高阶函数

所谓的高阶函数,是一种特殊的函数, 它接受函数作为参数, 或者返回一个函数.

fun test(a: Int, b: Int, sumSom: (Int, Int, Int) -> Int): Int {
    if (a > b) {
        return sumSom(0, a, 0)
    } else {
        return sumSom(0, b, 0)
    }
}

tailrec fun sumSom(start: Int, end: Int, result: Int): Int {
    var res = result
    var sta = start

    while (sta <= end) {
        res += sta
        sta++
    }

    return res
}

// 测试类
fun main(args: Array<String>) {
    println(test(10, 9, ::sumSom)) // Log:55
}

从上述代码,在函数test中,sumSom参数是一个函数类型:(Int, Int, Int) -> Int,其是一个函数,接受3个Int参数,返回值是一个Int类型的值。在test中,对传入的参数a,b进行判断,然后执行sumSom()函数并将执行结果返回。

函数类型(Function Type)

对于接受另一个函数作为自己参数的函数, 我们必须针对这个参数指定一个函数类型. 比如, 前面提到的test函数, 它的定义如下:

fun test(a: Int, b: Int, sumSom: (Int, Int, Int) -> Int): Int {
    if (a > b) {
        return sumSom(0, a, 0)
    } else {
        return sumSom(0, b, 0)
    }
}

参数sumSom的类型是(Int, Int, Int) -> Int,也就是说,它是一个函数,接受三个Int类型参数,并且返回一个Int。

Lambda表达式与匿名函数

Lambda 表达式, 或者匿名函数, 是一种”函数字面值(function literal)”, 也就是, 一个没有声明的函数, 但是立即作为表达式传递出去.

max(strings, { a, b -> a.length() < b.length() })

函数 max 是一个高阶函数, 也就是说, 它接受一个函数值作为第二个参数. 第二个参数是一个表达式, 本身又是另一个函数, 也就是说, 它是一个函数字面量. 作为函数, 它等价于:

fun compare(a: String, b: String): Boolean = a.length() < b.length() 

Lambda表达式

Lambda 表达式的完整语法形式, 也就是, 函数类型的字面值, 如下:

val sum = { x: Int, y: Int -> x + y }
  • Lambda 表达式用大括号括起,
  • 它的参数(如果存在的话)定义在 -> 之前 (参数类型可以省略),
  • (如果存在 -> 的话)函数体定义在 -> 之后.

如果Lambda 表达式只有唯一一个参数,在Kolin中可以自行判断出Lambda表达式的参数定义,此时允许我们省略唯一一个参数的定义, 并且会为我们隐含地定义这个参数, 使用的参数名为 it:

ints.filter { it > 0 } // 这个函数字面值的类型是 '(it: Int) -> Boolean'

如果一个函数接受另一个函数作为它的最后一个参数, 那么Lambda表达式作为参数时, 可以写在圆括号之外.

匿名函数

匿名函数看起来与通常的函数声明很类似, 区别在于省略了函数名,函数体可以是一个表达式(如上例), 也可以是多条语句组成的代码段:

fun(x: Int, y: Int): Int {
    return x + y
}

参数和返回值类型的声明与通常的函数一样, 但如果参数类型可以通过上下文推断得到, 那么类型声明可以省略:

ints.filter(fun(item) = item > 0)

对于匿名函数, 返回值类型的自动推断方式与通常的函数一样: 如果函数体是一个表达式, 那么返回值类型
可以自动推断得到, 如果函数体是多条语句组成的代码段, 则返回值类型必须明确指定(否则被认为是
Unit ).

闭包

对于闭包,不同的编程语言对其的定义是不一样,这里收集了一些:

  • 是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。-<

区别

  1. 是否指定返回值的类型

    • Lambda表达式返回值的类型通过以自动推断得到
    • 匿名函数的返回值类型必须手动指定,如果未指定返回值类型,默认返回值类型为Unit
  2. return的行为

    • Lambda 表达式内的 return 将会从包含这个Lambda表达式的函数中返回
    • 匿名函数内的 return 只会从匿名函数本身返
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值