# Kotlin教程 - 5 函数
函数也可以称之为方法、功能,我们可以将一段实现特定功能的代码封装为一个函数。
为什么需要封装为函数呢?
举个栗子:
给出两个点的坐标,计算出两个点的距离。
import kotlin.math.sqrt
fun main() {
val ax = 1.0 // 第一个点的坐标
val ay = 1.0
val bx = 2.0 // 第二个点的坐标
val by = 2.0
// 求两个点的距离,sqrt是开平方
val distance = sqrt((ax - bx) * (ax - bx) + (ay - by) * (ay - by))
println("distance:$distance");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如果在一个地方需要计算两个点的距离,就在一个地方写了这个功能的代码,100个、1000个地方需要这个功能,那就需要写1000遍。万一这个功能需要修改,修改1000个地方不得崩溃。
所以我们可以将这个功能封装为一个函数,想要使用到这个功能的时候,直接调用这个函数即可。
所以通过使用函数,提高代码的复用性,减少重复代码,提高开发效率。
我们之前已经使用了很多内置的函数,例如println()等,现在我们主要学习如何自定义函数。
# 5.1 函数的定义
函数定义的语法:
fun 函数名(参数列表): 返回值类型 {
函数体
return 返回值
}
2
3
4
fun
关键字用于定义函数;- 函数名,必须遵循标识符的命名规则,建议首字母小写的驼峰规则;
- 参数列表,包括参数名和参数类型,多个参数之间用逗号分隔。
- 返回值类型,指定函数返回的数据类型;
- 函数体,包含了实际的代码逻辑;
- 使用
return
语句返回函数的结果,如果没有返回值,return
可以省略。 - 参数可以缺省;
- 注意:return语句后面不能再有其他语句。
函数调用的语法:
函数名(参数列表)
举个栗子:
下面定义一个函数,计算两个数的和,然后调用该函数。
import kotlin.math.sqrt
/**
* 定义一个函数,计算两个点的距离,参数是两个点的坐标
*/
fun calculateDistance(ax: Double, ay: Double, bx: Double, by: Double): Double {
val distance = sqrt((ax - bx) * (ax - bx) + (ay - by) * (ay - by))
return distance
}
fun main() {
val ax = 1.0 // 第一个点的坐标
val ay = 1.0
val bx = 2.0 // 第二个点的坐标
val by = 2.0
// 调用函数calculateDistance,传入参数,计算两个坐标的距离
val distance = calculateDistance(ax, ay, bx, by)
println("distance:$distance");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
首先定义了 calculateDistance
函数,函数接收4个参数,也就是两个点的坐标,在函数内,通过坐标计算出距离,然后通过 return
语句返回。
然后在 main 函数中,调用了 calculateDistance
函数,在调用的时候,传递了4个参数,同时定义了一个 distance
常量接收返回的数值。
定义函数时,函数名后面的参数叫形式参数(形参),函数的参数个数不限,使用逗号分隔;
调用函数的时候,传递的是实际参数(实参),表示函数执行的时候的参数值。
函数可以没有参数,如果有参数,参数必须指定类型。
也可以没有返回值,没有返回值的时候,返回值类型可以省略。
举个栗子:
/**
* 定义sayHello函数,只是打印了一句字符串
*/
fun sayHello() {
println("Hello")
}
fun main() {
// 调用sayHello函数
sayHello()
}
2
3
4
5
6
7
8
9
10
11
上面定义了一个 sayHello
函数,函数没有参数,没有返回值,没有返回值类型。
# 5.2 函数的参数
# 1 默认参数
默认参数为形参提供默认值,那么在调用函数的时候,如果不传递参数,则使用默认参数值。
举个栗子:
fun sayHello(name: String, age: Int = 3, gender: String = "男") {
println("Helo, 我是 $name,我 $age 岁了, 我是 $gender 人")
}
fun main() {
sayHello("Shabi", 8, "男") // 调用函数
sayHello("Niubi") // 也可以不传递参数
}
2
3
4
5
6
7
8
指定了默认参数,如果不传递参数,那么参数会使用指定的默认值。
上面传递参数的时候是通过位置传递参数的,也就是说在调用函数的时候,第一个参数是传递给函数的第一个参数,第二个传递给第二个,依此类推,这叫位置传参。
这样的话,没有默认值的参数需要放在前面,这样有默认值的才能不传递参数。
举个栗子:
fun sayHello(age: Int = 3, gender: String = "男", name: String) {
println("Helo, 我是 $name,我 $age 岁了, 我是 $gender 人")
}
2
3
因为 name 参数没有默认值,所以必须传值给 name,但是 name 参数在第三个位置,所以前两个位置的参数也必须传递值,不能省略。如果将 name 放到第一个位置,后面两个参数才可以不传值。
如果不想被位置约束,可以使用下面的命名参数来传值。
# 2 命名参数
命名参数可以不按顺序传递参数,按照参数的名称来传递。
举个栗子:
fun sayHello(age: Int = 3, gender: String = "男", name: String) {
println("Helo, 我是 $name,我 $age 岁了, 我是 $gender 人")
}
fun main() {
sayHello(name="Doubi", gender = "女") // 通过名称传递参数
// sayHello(gender = "女") // name必须传递,因为name没有默认值
}
2
3
4
5
6
7
8
命名参数主要是针对调用而言的,调用的时候,根据名称来传递即可。
# 3 可变数量参数
可变数量参数允许定义接收不定数量的相同类型参数的函数。可变数量参数使用 vararg
关键字来定义。
举个栗子:
/**
* 定义一个可变数量参数的函数
*/
fun printNumbers(vararg numbers: Int) {
for (num in numbers) {
println(num)
}
println(numbers[0]) // 取出第一个参数
println(numbers[1]) // 取出第二个参数
}
fun main() {
// 调用带有可变数量参数的函数,参数的个数没有限制
printNumbers(1, 2, 3)
printNumbers(1, 2, 3, 4, 5)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在上面定义了一个可变数量参数的函数 printNumbers
,参数 numbers
其实是一个数组(后面在讲解,就是里面装了多个相同类型的数值),可以使用 for 循环遍历参数,也可以使用 下标
来取出指定位置的参数。(下标从0开始)。
需要注意,一个函数只能有一个可变数量的参数,并且可变数量参数必须是函数的最后一个参数。
# 4 特殊字符的函数名
我们之前说函数名必须遵守标识符命名规则,但是因为 Kotlin 和 Java 是可以相互调用的,但是 Kolin 和 Java 中的关键字不是完全相同的,所以如果使用 Kotlin 来调用 Java,但是在 Java 中合法的函数名在 Kotlin 中是关键字,那么 Kotlin 中就无法调用了。
这个时候可以使用反引号(键盘上1左边的按键)将其括起来,这样可以创建具有非标准命名的函数或变量。
举个栗子:
/**
* 定义了一个特殊字符的函数名函数
*/
fun `when`() {
println("调用when函数")
}
fun main() {
`when`() // 调用函数
}
2
3
4
5
6
7
8
9
10
在 Kotlin 中 when
是关键字,不能单独用来做标识符,但是可以时候用反引号括起来,这样就可以作为函数名了,调用的时候,也是需要使用反引号的。
甚至还可以使用汉字来定义函数名:
fun `定义了一个被人看见可能会被打的函数名`() {
println("被人打了别怪我")
}
fun main() {
`定义了一个被人看见可能会被打的函数名`() // 调用函数
}
2
3
4
5
6
7
有些公司为了保密,将函数名都定义为数字,然后通过查阅文档才能知道这个函数的功能,起到保密的作用,当然这样做的很少很少,基本不会用,知道就好,那么可以使用数字来定义函数。
fun `10086`() {
println("我是中国移动")
}
fun main() {
`10086`() // 调用函数
}
2
3
4
5
6
7
# 5 函数的重载
什么是函数的重载?
函数的重载就是函数的方法名一样,但是参数不一样,注意不包括返回值。
举个栗子:
fun sayHello() {
println("没有参数")
}
fun sayHello(name: Int) {
println("有一个整型参数")
}
fun sayHello(name: String) {
println("有一个字符串参数")
}
fun sayHello(name: String, age: Int) {
println("多个参数")
}
fun main() {
sayHello()
sayHello(12)
sayHello("Doubi")
sayHello("Doubi", 12)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上面的几个函数,方法名一样,但是参数不一样,这就是函数的重载。编译器会根据传递的参数类型和数量来选择正确的函数。
当有默认参数的时候,看看下面的代码:
fun sayHello() {
println("没有参数")
}
fun sayHello(name: Int = 12) {
println("有一个整形参数")
}
fun sayHello(name: String = "Doubi") {
println("有一个字符串参数")
}
fun main() {
sayHello() // 没有传递参数,会调用哪一个函数?
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
调用的时候没有传递参数,会调用 没有参数
的函数。
如果将没有参数的函数删掉呢?
fun sayHello(name: Int = 12) {
println("有一个字符串参数")
}
fun sayHello(name: String = "Doubi") {
println("有一个字符串参数")
}
fun main() {
// sayHello() // 报错
}
2
3
4
5
6
7
8
9
10
11
此时代码会报错,因为 sayHello()
不知道调用哪个函数了,因为上面两个函数都可以调用。
# 5.3 函数的返回值
# 1 无返回值类型
在前面已经讲解了,如果函数没有返回值,返回值的类型是可以省略的。
那么如果不省略,没有返回值的时候,返回值类型是什么呢?
在Java中是void,在Kotlin中是 Unit。
举个栗子:
/**
* 没有返回值的函数
*/
fun sayHello() {
println("Hello World")
}
fun main() {
println(sayHello())
}
2
3
4
5
6
7
8
9
10
执行结果:
Hello World kotlin.Unit
函数没有返回值,打印的结果是 kotlin.Unit
。
当然,没有返回值的时候,我们也可以显式的定义返回值类型,如下:
/**
* 没有返回值的函数
*/
fun sayHello(): Unit {
println("Hello World")
}
fun main() {
sayHello();
}
2
3
4
5
6
7
8
9
10
上面定义了一个没有返回值的函数 sayHello
,返回值的类型是 Unit,不过一般都是省略不写了。
# 2 单表达式函数
如果函数体只包含单个表达式,并且编译器可以根据表达式的类型自动推断返回值类型,那么可以省略返回值类型和大括号 {}
。
举个栗子:
fun add(a: Int, b: Int) = a + b
fun main() {
println(add(1, 2)) // 调用函数并打印结果
}
2
3
4
5
上面的 add 函数的函数体只有一句表达式,那么可以省略返回值类型和大括号 {}
。
单个表达式都可以,因为 println()
函数没有返回值,所以下面的函数返回值类型是 Unit
:
fun sayHello() = println("Hello Doubi!") // 打印Hello Doubi
fun main() {
sayHello()
}
2
3
4
5
# 5.4 函数的嵌套调用
函数的嵌套调用就是一个函数可以调用另外一个函数,另外一个函数还可以继续调用其他的函数,依此类推。
举个栗子:
下面定义了2个函数,funA() 和 funB(),并在main()函数中调用了funA() ,然后在funA()中调用了 funB()。
fun funB() {
println("----b")
}
fun funA() {
println("----a1")
funB()
println("----a2")
}
fun main() {
funA()
}
2
3
4
5
6
7
8
9
10
11
12
13
执行结果:
----a1 ----b ----a2
我们会发现在 funA() 中调用 funB() 后,funB() 执行完成,重新回到了 funA() 继续执行。
# 5.5 变量的作用域
变量的作用域就是在哪里可以使用这个变量。不可能我们随便定义了一个变量,哪里都可以使用。
变量主要分类两类:局部变量和全局变量。
变量的作用域是根据代码的结构 {}
来决定作用域范围的,优先使用自己作用域中的变量,如果没有找到,则一层层向外查找。
# 1 局部变量
局部变量就是在函数内部定义的变量,这样变量只能在变量内部使用。
举个例子:
fun funA() {
val num = 3
print(num)
}
fun main() {
// print(num); // 无法访问到funA()内定义的变量num
}
2
3
4
5
6
7
8
因为变量 num
在 funA()
函数内部定义,所以在 main
函数无法访问。
局部变量当函数调用完成,就被销毁释放了。
# 2 全局变量
全局变量就是在函数内、外都内调用的变量。
举个栗子:
var a = 1 // 定于全局变量a
var b = 2 // 定于全局变量b
fun main() {
val a = 5
run { // 创建了一个临时局部作用域
val a = 10
println(a) // 优先在所在的{}内查找变量
}
println(a) // 优先在所在的{}内查找变量,即main()函数内查找a
println(b) // 优先在所在的{}内查找变量,main函数内没找到,则继续向外寻找
}
2
3
4
5
6
7
8
9
10
11
12
val a = 10;
只在它所在的 {}
内有效,{}
外是无法访问的。
执行结果:
10
5
2
# 5.6 函数的递归
什么是函数的递归?
函数的递归,就是在一个函数内部,又调用了这个函数自己,这个就是函数的递归。
举个例子:
fun funA() {
print("----")
funA()
}
fun main() {
funA()
}
2
3
4
5
6
7
8
上面的函数,执行 funA(),然后在 funA() 内部又调用了自己,那么会重新又调用了 funA() 函数,然后又调用了自己,这样就会一直无限调用,变成了无限循环调用,执行的时候很快就报Stack Overflow的错误,栈溢出。
所以函数的递归有时候是很危险的,很容易无限调用,造成栈溢出,程序崩溃。所以函数的递归调用一定要注意结束或跳出递归的条件。
例如我们写一个用递归求阶乘的函数:
fun factorial(num: Int): Int {
// 求阶乘
if (num <= 1) {
return 1
} else {
return num * factorial(num - 1)
}
}
fun main() {
val num = 5
val result = factorial(num)
print("${num}的阶乘为:${result}")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
以 5 举例,求 5 的阶乘,调用了 factorial函数
,则计算 5 乘以 4 的阶乘,然后求 4 的阶乘,重新调用了 factorial函数
,然后计算 4 乘以 3 的阶乘,一次类推,一直得到1的阶乘,然后向上返回。
递归函数一定得有结束的条件,否则就会无限递归导致栈溢出错误。