顶层函数
顶层函数又称为包级别函数,可以直接放在某一个包中,而不像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()函数是一个普通的递归函数。由于方法的递归调用过程比较复杂,接下来我们通过一个图例来分析整个调用过程,如图所示。
尾递归函数
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的数字之和。
需要注意的是,函数的重载与函数的返回值类型无关,只需要同时满足两个条件,一是函数名相同,二是参数个数或参数类型不相同即可。
酷客教程相关文章:
评论前必须登录!
注册