# 函数
## 函数声明
Kotlin 中函数使用 `fun` 关键字声明
``` kotlin
fun double(x: Int): Int {
}
```
## 函数用法
使用传统方法调用函数
``` kotlin
val result = double(2)
```
使用点标记调用成员函数
``` kotlin
Sample().foo() // 创建 Sample 实例并调用 foo
```
### infix 符号
在下列情况下函数还可以使用 `infix` 符号调用
* 成员函数或者 [扩展函数](extensions.html)
* 有一个单独的参数
* 被 `infix` 关键字所标记
``` kotlin
// 定义 Int 扩展
infix fun Int.shl(x: Int): Int {
...
}
// 使用 infix 标记调用扩展函数
1 shl 2
// 含义与下面一样
1.shl(2)
```
### 形参
函数形参使用 Pascal 标记法定义,例如:*name*: *type*。形参使用逗号分隔。每个参数必须有明确的类型。
``` kotlin
fun powerOf(number: Int, exponent: Int) {
...
}
```
### 默认参量
在相应的参量被省略的地方,函数参数可以有默认值。这会减少与其它语言的差异。
``` kotlin
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) {
...
}
```
> 使用紧接在类型后面的 **=** 定义默认值
### 命名参量
调用函数时可以为函数参数命名。这非常适合于某些有很多参数或者有一个默认参数的函数。
请看下面给出的方法
``` kotlin
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
```
我们可以使用默认值调用
``` kotlin
reformat(str)
```
然而,当要以非默认值调用它时,就得像这样:
``` kotlin
reformat(str, true, true, false, '_')
```
与命名参量一起我们可以让代码更具可读性
``` kotlin
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
```
并且如果我们不需要所有的参量
``` kotlin
reformat(str, wordSeparator = '_')
```
>[danger]注意命名参量语法不能在调用 Java 方法时使用,因为 Java 字节码不会一直保持函数参数的名称。
### 返回 `Unit` 的函数
如果一个函数不返回有用的值,它的返回类型就是 `Unit`。`Unit` 是一个仅有一个 `Unit` 值的类型。这个值不用明确地返回。
``` kotlin
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit` 或者 `return` 是可选的
}
```
声明 `Unit` 返回类型同样也是可选的。上面的代码等同于下面
``` kotlin
fun printHello(name: String?) {
...
}
```
### 单表达式函数
当一个函数返回单个表达式时,可以省略花括号,函数体在一个 `=` 符号后指定
``` kotlin
fun double(x: Int): Int = x * 2
```
在编译器能推断时,明确地声明返回类型是[optional](#explicit-return-types)的。
Explicitly declaring the return type is [optional](#explicit-return-types) when this can be inferred by the compiler
``` kotlin
fun double(x: Int) = x * 2
```
### 明确的返回类型
有其它块的函数必须明确地指定返回类型,除非是要它们返回 `Unit`,[which 流可选](#unit-returning-functions)。Kotlin 无法为有块体的函数推断出返回类型,因为这类函数的函数体可能有复杂的控制流,并且返回类型对于阅读器来说不太明确(编译器也无法理解)。
### 可变数量的参量(Varargs)
函数的某个参数(一般是最后一个)可以被修饰语 `vararg` 一起被标记:
``` kotlin
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
```
允许向函数传递可变数量的参量:
```kotlin
val list = asList(1, 2, 3)
```
类型为 `T` 的 `vararg` 参数在函数内部会以一个 `T` 数组呈现,例如在上面的示例中的 `ts` 变量的类型是 `Array<out T>`。
只有一个参数可以被标记为 `vararg`。如果一个 `vararg` 参数不是最后一个参数,那么后面的参数值可以使用命名参量语法,或者这个参数通过传递一个 lambda 外部插入而获得一个函数类型。
当我们调用一个 `vararg` 函数时,我们既可以一个一个地传递参量,例如 `asList(1, 2, 3)`,又或者,如果我们已经有了一个数组并想要把它的内容传递给函数,可以使用**展开**操作(给数组加上 `*` 前缀):
```kotlin
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
```
## 函数作用域
可以在文件顶层声明 Kotlin 函数,即你不需要创建一个类来保存一个函数,类似于像 Java, C# 或 Scala 一样的编程语言。除了顶层函数外,Kotlin 函数还可以和成员函数和扩展函数一样被声明为本地。
### 本地函数
Kotlin 支持本地函数,例如一个函数在另一个函数内部
``` 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` 可以是一个本地变量
``` kotlin
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
```
### 成员函数
成员函数是定义在某个类或对象内部的函数
``` kotlin
class Sample() {
fun foo() { print("Foo") }
}
```
成员函数用句点标记调用
``` kotlin
Sample().foo() // 创建 Sample 实例并调用 foo
```
更多关于类和覆盖成员的信息,查看[类](classes.html) 和[继承](classes.html#inheritance)章节
## 泛型函数
函数可以有泛型参数,在函数名前面用尖括号指定。
``` kotlin
fun <T> singletonList(item: T): List<T> {
// ...
}
```
更多关于泛型函数的信息查看[泛型](generics.html)章节
## 内联函数
关于内联函数的解释在[这里](inline-functions.html)。
## 扩展函数
关于扩展函数在[专门的章节](extensions.html)中解释。
## 高阶函数与 Lambdas
高阶函数与 Lambdas 在 [专门的章节](lambdas.html)中解释
## 尾部递归函数
Kotlin 支持一种叫做[尾部递归](https://en.wikipedia.org/wiki/Tail_call)的编程风格。这允许一些通常意义上的使用递归函数替代循环的算法,但不会有堆栈溢出的风险。
当一个函数被 `tailrec` 修饰并遇到符合的形式,编译器会优化递归,替代为一个快速并有效率的基于循环的版本。
``` kotlin
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
```
这段代码为一个精确的常量计算其余弦的定点。它从 1.0 开始简单地重复调用 `Math.cos`,直到结果不再改变,产生了一个 0.7390851332151607 的结果。所产生等价的传统风格代码为:
``` kotlin
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y) return y
x = y
}
}
```
`tailrec` 修饰语正确的用法是,一个函数调用自身必须是最后才能做的事情。你不能在递归之后还有更多代码时使用尾部递归,并且你不能与 `try`/`cathch`/`finally` 块一起使用。当前尾部递归仅支持 JVM 后端。