# 函數
## 函數聲明
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 后端。