Kotlin 函数的分类

顶层函数

顶层函数又称为包级别函数,可以直接放在某一个包中,而不像Java一样必须将函数放在某一个类中。在Kotlin中,函数可以独立存在,之前写过的很多函数都是顶层函数,例如经常用的main()函数。顶层函数在被调用时,如果在同一个包中,可直接调用,如果在不同的包中,需要导入对应的包。接下来我们通过一个案例来详细地讲解顶层函数。

创建一个cn.itcast.chapter03包,在该包中创建一个StudentInfo.kt文件,在该文件中创建一个stuInfo()函数,该函数主要是传递两个参数,并打印这两个参数,具体代码如下所示。

package cn.itcast.chapter03
fun stuInfo(name: String, age: Int) {
    println(" 姓名:$name")
    println(" 年龄:$age")
}

com.itheima.chapter03包中,创建一个Score.kt文件,在该文件中创建一个sum()函数和一个main()函数,其中sum()函数用于计算考试成绩,该函数主要传递3个Int类型的参数math、chinese、english,在main()函数中调用cn.itcast.chapter03包中的stuInfo()函数以及自身包中的sum()函数,具体代码如下所示。

package com.itheima.chapter03
import cn.itcast.chapter03.stuInfo
fun sum(math: Int, chinese: Int, english: Int) {
    var result = math + chinese + english
    println(" 成绩:$result")
}
fun main(args: Array<String>) {
    stuInfo(" 江小白", 18)    // 调用cn.itcast.chapter03 包中的stuInfo()函数
    sum(100, 9, 100)
}

运行结果:

姓名:江小白
年龄:18
成绩:209

从上述代码可以看出,在main()函数中调用cn.itcast.chapter03包中的stuInfo()函数时,需要使用“import cn.itcast.chapter03.stuInfo”导入该函数,当调用sum()函数时,由于该函数与main()函数在同一个包中,因此不需要导包,直接调用即可,最后将两个函数的结果输出。

成员函数

在Kotlin中,除了顶层函数之外,根据函数作用域还可以将函数分为成员函数、局部函数。成员函数是在类或对象内部定义的函数,成员函数的语法格式如下:

class类名{ 
    fun 函数名(){ 
        执行语句 
        … 
    } 
}

在上述语法格式中,class是定义类的一个关键字。当在main()函数中调用成员函数时,需要使用“类的实例.成员函数()”的形式。接下来我们通过一个案例来创建一个成员函数并调用该函数,具体代码如下所示。

package com.itheima.chapter03
class Person {          // 创建一个Person 类
    fun hello() {       // 定义一个成员函数
        print("Hello")
    }
}
fun main(args: Array<String>) {
    var person: Person = Person()       // 创建Person 类的实例
    person.hello()                      // 调用Person 类中的成员函数hello()
}

运行结果:

Hello

在上述代码中,创建了一个名为Person的类,在该类中创建了一个成员函数hello(),在该函数中通过print()语句打印“Hello”字符串。在main()函数中,创建一个Person类的实例person,接着通过“.”调用成员函数hello()。

局部函数

局部函数又称嵌套函数,主要是在一个函数的内部定义另一个函数。局部函数的语法格式如下:

fun 函数名(){ 
    fun 函数名(){ 
        执行语句 
        … 
    } 
    执行语句 
    … 
}

下面我们根据上述格式,来创建一个局部函数,具体代码如下所示。

package com.itheima.chapter03
fun total(a: Int) {       //定义一个函数total()
    var b: Int = 5        //定义一个变量b,并将其初始值设置为5
    fun add(): Int {      //定义个局部函数add()
        return a + b      //返回变量a 与b 的和
    }
    println(add())
}
fun main(args: Array<String>) {
   total(3)             //调用total()函数,传递实参为3
}

运行结果:

8

在上述代码中,定义了一个total()函数,在该函数中传递了一个Int类型的参数a,并定义了一个变量b与一个局部函数add()。由于add()函数的返回值是变量a与b的和,说明局部函数add()可以访问外部函数total()中的局部变量a与b。在第7行代码中,调用局部函数add()并输出该函数的返回值,说明在外部函数total()中可以调用其内部的局部函数add()。最后在main()函数中调用total()函数,并向该函数传递一个实参3。

由上述代码可知,局部函数可以访问外部函数的局部变量,并且在外部函数中可以调用其内部的局部函数。

递归函数

递归函数指的是在函数体内部调用函数本身。递归函数可以用少量的代码实现需要多次重复计算的程序。接下来我们通过一个递归函数求1~100的和,具体代码如下所示。

package com.itheima.chapter03
fun sum(num: Int): Int {           //定义一个sum()函数
    if (num == 1) {                //num 为1 时,则指定返回值为1
        return 1
    } else {
        return num + sum(num - 1)  //num 不为1 时,返回num 与sum()返回值之和
    }
}
fun main(args: Array<String>) {
   println(sum(4))                //调用递归函数
}

运行结果:

10

在上述代码中,首先定义了一个sum()函数,该函数是用于求1~4的数字之和,如果传递到该函数中的参数为1时,该函数会返回1,如果传递到该函数中的参数不为1时,则返回参数num与函数sum()返回值之和。由于在第6行代码中调用了函数本身,因此这个sum()函数是一个普通的递归函数。由于方法的递归调用过程比较复杂,接下来我们通过一个图例来分析整个调用过程,如图所示。
Kotlin 函数的分类

尾递归函数

1. 尾递归函数的定义
如果一个函数中所有递归调用都出现在函数的末尾,我们称这个递归函数是尾递归函数。尾递归函数的特点是在递归过程中不用做任何操作,当编译器检测到一个函数调用是尾递归函数时,它就覆盖当前的活动记录而不是在栈中去创建一个新的。因为递归调用是当前活跃期内最后一条待执行语句,于是当调用返回时栈中没有其他事情可做,因此也就没有保存的必要。这样可以大大缩减所使用的栈空间,使得程序运行效率变得更高。虽然编译器能够优化尾递归造成的栈溢出问题,但是在编程中还是应该尽量避免尾递归的使用。

尾递归函数是一种特殊的递归函数,特殊之处在于该函数的调用出现在函数的末尾。通常情况下,尾递归函数一般用在连续求和、连续求差等程序中。接下来我们将递归函数中的普通递归函数修改成尾递归函数,具体代码如下所示。

package com.itheima.chapter03
fun sum(num: Int, total: Int = 0): Int { //尾递归函数
    if (num == 1) {
        return 1 + total
    } else {
        return sum(num - 1, num + total)
    }
}
fun main(args: Array<String>) {
   println(sum(100))          // 调用尾递归函数
}

运行结果:

5050

在上述代码中,sum()函数比递归函数中的sum()函数多了一个total参数,这个参数主要用于累加每次传递到函数sum()中的num值,当num值为1时,则将“1+total”的值作为该尾递归函数的返回值。由于在第6行代码中,只调用了该函数本身作为整个函数的最后一条执行语句,因此sum()函数是一个尾递归函数。

2. 尾递归函数的优化
在Kotlin中,尾递归函数一般会循环调用,当调用次数过多时,程序会出现栈溢出的问题,为了解决这个问题,Kotlin中提供了一个tailrec修饰符来修饰尾递归函数,此时编译器会优化该尾递归函数,将尾递归函数转化为while循环,程序会快速高效地运行,并且无堆栈溢出的风险。

如果将上面中的sum()函数传递的参数num设置为100000,则程序在运行时会出现内存溢出的问题。这时就需要使用tailrec修饰符来修饰该尾递归函数,来避免出现内存溢出的问题。修改后的代码如下所示。

package com.itheima.chapter03
tailrec fun sum(num: Int, total: Int = 0): Int {
    if (num == 1) {
        return 1 + total
    } else {
        return sum(num - 1, num + total)
    }
}
fun main(args: Array<String>) {
   println(sum(100000))          // 调用尾递归函数
}

运行结果:

705082704

在上述代码中,在main()函数中调用sum()函数时,向sum()函数中传递的参数设置为100000,如果此时运行该程序,则会由于程序的循环次数过多而产生内存溢出的问题,为了避免出现这个问题,在sum()函数之前添加一个tailrec修饰符,这个修饰符是用来优化尾递归函数sum()的。

函数重载

无论是Java语言还是C++语言,都会有函数重载,函数重载主要是针对不同功能业务的需求,暴露不同参数的接口,包括参数列表个数、参数类型等。这些参数不同的调整会增加多个同名函数,这样在程序中调用这些函数时容易出现调用错误。但是Kotlin语言就在这个方面优于Java语言与C++语言,因为这门语言在语法上比较明确,并且还存在函数命名参数与默认值参数,这样就可以彻底消除函数重载时容易出现调用出错的问题。

函数重载一般是用在功能相同但参数不同的接口中,例如最简单的四则运算操作——加、减、乘、除,我们以加法为例,结合递归函数尾递归函数的案例来讲解函数重载的使用,具体代码如下所示。

package com.itheima.chapter03
/ **
  *  定义一个函数totalNum(),函数有1 个参数,参数类型为Int
  * /
fun totalNum(num: Int): Int {
    if (num == 1) {
        return 1
    } else {
        return num + totalNum(num - 1)
   }
}
/ **
 * 定义一个totalNum()函数,函数有1 个参数,参数类型为Float
 * /
fun totalNum(num: Float): Float {  //重载函数参数类型不同
   if (num == 1F) {
       return 1F
   } else {
       return num + totalNum(num - 1F)
   }
}
/ **
 * 定义一个totalNum()函数,函数有2 个参数,参数类型是Int
 * /
fun totalNum(num: Int, total: Int = 0): Int { //重载函数参数个数不同
   if (num == 1) {
       return 1 + total
   } else {
       return totalNum(num - 1, num+ total)
   }
}
fun main(args: Array<String>) {
   var a1 = totalNum(5)
   var a2 = totalNum(5F)
   var a3 = totalNum(5, 0)
   println("a1=" + a1)
   println("a2=" + a2)
   println("a3=" + a3)
}

运行结果:

a1=15
a2=15.0
a3=15

上述代码中,定义了3个同名函数totalNum(),它们的参数个数或类型不同,从而形成了函数的重载,在main()函数中调用totalNum()函数时,根据传递不同的参数来确定调用的是哪个重载函数,如调用totalNum(5)函数,根据该函数中传递的参数5为Int类型可知,此时调用的是参数为1个Int类型的totalNum()函数,该函数主要是通过递归函数求1~5的数字之和。

需要注意的是,函数的重载与函数的返回值类型无关,只需要同时满足两个条件,一是函数名相同,二是参数个数或参数类型不相同即可。

酷客教程相关文章:

赞(0)

评论 抢沙发

评论前必须登录!