默认参数
如果在默认参数之后的最后一个参数是 lambda 表达式,那么它既可以作为具名参数在括号内传入,也可以在括号外传入:
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*……*/ }
foo(1) { println("hello") } // 使用默认值 baz = 1
foo(qux = { println("hello") }) // 使用两个默认值 bar = 0 与 baz = 1
foo { println("hello") } // 使用两个默认值 bar = 0 与 baz = 1
当一个函数调用混用位置参数与具名参数时,所有位置参数都要放在第一个具名参数之前。例如,允许调用 f(1, y = 2) 但不允许 f(x = 1, 2)。
可以通过使用星号操作符将可变数量参数(vararg) 以具名形式传入(vararg 表示的是数组 ,跟java的... 差不多):
fun foo(vararg strings: String) { /*……*/ }
foo(strings = *arrayOf("a", "b", "c"))
对于 JVM 平台:在调用 Java 函数时不能使用具名参数语法,因为 Java 字节码并不总是保留函数参数的名称。
当返回值类型可由编译器推断时,显式声明返回类型是可选的:
fun double(x: Int): Int = x * 2
可以简写为
fun double(x: Int) = x * 2
#中缀表示法(骚操作 ,不用)
标有 infix 关键字的函数也可以使用中缀表示法(忽略该调用的点与圆括号)调用。中缀函数必须满足以下要求:
它们必须是成员函数或扩展函数; 它们必须只有一个参数; 其参数不得接受可变数量的参数且不能有默认值。
infix fun Int.shl(x: Int): Int { …… }
// 用中缀表示法调用该函数
1 shl 2
// 等同于这样
1.shl(2)
局部函数
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
局部函数可以访问外部函数(即闭包)的局部变量,所以在上例中,visited 可以是局部变量:
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if(!visited.add(current)) return
for(v in current.neighbors) {
dfs(v)
}
}
}
尾递归函数
调用完自己以后没有任何操作了,就叫尾递归
// 这个不是尾递归,因为调用完成之后还有其他的操作
fun factorial(n:Long):Long{
return n * factorial(n - 1)
}
Kotlin 支持一种称为尾递归的函数式编程风格。 这允许一些通常用循环写的算法改用递归函数来写,而无堆栈溢出的风险。
当一个函数用 tailrec 修饰符标记并满足所需的形式时,编译器会优化该递归,留下一个快速而高效的基于循环的版本:
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
这段代码计算余弦的不动点(fixpoint of cosine),这是一个数学常数。 它只是重复地从 1.0 开始调用 Math.cos,直到结果不再改变,对于这里指定的 eps 精度会产生 0.7390851332151611 的结果。最终代码相当于这种更传统风格的代码:
val eps = 1E-10 // "good enough", could be 10^-15
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (Math.abs(x - y) < eps) return x
x = Math.cos(x)
}
}