> For the complete documentation index, see [llms.txt](https://kuroclass.gitbook.io/kotlin/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kuroclass.gitbook.io/kotlin/ch6/shi-qing-bian-de-yue-lai-yue-you-qu-fun-liao-ne.md).

# 事情變得越來越有趣(fun)了呢！

到目前為止我們全部都是在main函數裡面寫程式碼，這樣的作法在我們學習的時候很方便。可是在現實世界裡面，我們在撰寫一整個專案的時候，不太會把所有的程式碼全部寫在main函式裡。而是會用很多其他的方法把程式碼拆開來，方便區分不同程式碼的用途，也方便我們閱讀、管理。其中一個方法就是使用函數。

## 要怎麼建立函數？

要宣告函數我們會需要使用到“fun”這個關鍵字，所以如果我們需要宣告一個叫做“printBusTimeInfo”的函數，我們會這樣寫：

```kotlin
fun printBusTimeInfo() {
    // 程式碼放這裡
}
```

那我們要怎麼使用定義好的程式碼呢？可以這樣做：

```kotlin
fun main() {
    printBusTimeInfo()
}
```

## 獨樂樂不如眾樂樂

當然，函數並不是只有上面這樣這麼簡單而已，與其什麼都沒有自己在裡面high，不如放很多東西進去大家在裡面一起high～例如如果我們想要在printBusTimeInfo這個函數輸入一個“型態是Int的時間”那我們可以改變一下宣告的方式：

```kotlin
fun printBusTimeInfo(busArriveMinute: Int) {
    // 程式碼放這裡
}
```

這樣我們在呼叫的時候就可以把一個Int值傳入函數裡面：

```kotlin
fun main() {
    printBusTimeInfo(5)
}
```

> 這裡有個值得注意的地方，如果函數有參數，我們在呼叫的時候就必須要傳入“函數規定的東西”。像上面的printBusTimeInfo函數，有一個Int參數，那我們就必須“剛剛好”傳入“一個Int”給他，不能多也不能少，型態也不能不同，這樣才能成功呼叫。像下面就是一個錯誤的呼叫方式：
>
> ```kotlin
> fun main() {
>     printBusTimeInfo("6分鐘")
> }
> ```

### 也可以把很多東西都塞進去函數裡

函數可以有很多很多參數，我們只需要把參數全部寫在“( )”裡，並用“,”隔開就可以，呼叫函數的時候也是一樣，需要遵守規則：

```kotlin
fun main() {
    printBusTimeInfo(5, true)
}

fun printBusTimeInfo(busArriveMinute: Int, shouldStop: Boolean) {
    // 程式碼放這裡
}
```

我們也可以把符合型態的變數穿給函數

```kotlin
fun main() {
    var minute = 5
    var shouldStop = true
    printBusTimeInfo(minute, shouldStop)
}
```

#### 參數與引數

我們會用參數(parameter)跟引數(argument)來稱呼丟進去函數喇揍會的東西，在學習一些更深層的理論的時候我們會使用很嚴謹的名詞去定義。但是現在這個階段，我們還沒有必要這麼嚴格的去區分這些東西，我們先學會大致上如何區分就好，記住一個原則：

> **函數區塊裡面使用的是“參數”，傳進去參數的東西叫做“引數”。**

我們會把一些數值(例如上面的5跟true、或是傳進去變數)作為“引數”傳給“參數”。而“參數”就是一個能夠在函數裡面使用的區域變數這樣。

### 指定這個引數要傳給誰

我們在呼叫函數的時候傳入的引數會依序傳遞給相對應的參數，而Kotlin提供了一種可以指定這個引數要傳給哪一個參數的方法，我們來看看要怎麼使用：

```kotlin
fun main() {
    var minute = 5
    var shouldStop = true
    printBusTimeInfo(busArriveMinute = minute, shouldStop = shouldStop)
    printBusTimeInfo(shouldStop = shouldStop, busArriveMinute = minute) //這樣寫嘛OK
}
```

指定傳入引數有一個好處，就是我們可以不需要依照定義的順序傳入引數，這樣在函數的參數超級無敵多的時候，哪個引數是要傳給誰就能夠一目了然，比較不容易傳錯。

### 預設引數值

有時候我們在大多數呼叫函數的時候會傳入相同的值，例如範例裡面，絕大多數的公車都是每一站都應該要停，只有少部分的站點有區分有些車會停有些車不會停。那我們就可以把傳入shouldStop的引數預設為true：

```kotlin
fun printBusTimeInfo(busArriveMinute: Int, shouldStop: Boolean = true) {
    // 程式碼放這裡
}
```

那這樣我們在呼叫這個函數的時候，就可以省略shouldStop不寫：

```kotlin
fun main() {
    printBusTimeInfo(busArriveMinute = 6)
    printBusTimeInfo(busArriveMinute = 6, shouldStop = false)
}
```

## 叫函數吐東西出來

我們可以函數可以回傳值出來，接下來我們來看看，把printBusTimeInfo函數拆分的更細。現在只需要這個函數傳出文字就好，不負責輸出的話，要怎麼做：

```kotlin
fun getBusTimeInfo(busArriveMinute: Int, shouldStop: Boolean = true): String {
    return if (shouldStop) {
        when (busArriveMinute) {
            0 -> "進站中"
            in 1..3 -> "將到站"
            else -> "${busArriveMinute}分鐘"
        }
    } else {
        "不停靠"
    }
}
```

跟宣告參數一樣，如果我們宣告了函數的回傳型態，我們就必須回傳相同型態的東西，不能宣告了卻不回傳，或是回傳不同型態的值。

### 如果函數裡面只有一句話......

如果一個函數裡面只有一個運算式，我們可以簡化我們的函數變成這樣：

```kotlin
fun getBusTimeInfo(busArriveMinute: Int, shouldStop: Boolean = true): String =
    if (shouldStop) {
        when (busArriveMinute) {
            0 -> "進站中"
            in 1..3 -> "將到站"
            else -> "${busArriveMinute}分鐘"
        }
    } else {
        "不停靠"
    }
```

而且還記得類型推斷嗎？簡化了的函數也適用類型推斷！所以我們可以再更進一步把回傳型別省略變成：

```kotlin
fun getBusTimeInfo(busArriveMinute: Int, shouldStop: Boolean = true) =
    if (shouldStop) {
        when (busArriveMinute) {
            0 -> "進站中"
            in 1..3 -> "將到站"
            else -> "${busArriveMinute}分鐘"
        }
    } else {
        "不停靠"
    }
```

> ‘不是說一句話才能省略嗎？這個明明這麼多句話，為什麼也可以省略？’
>
> “一句話跟一行是不一樣的概念喔！當然在Kotlin裡面一行一定會是一句話沒錯，可是還有很多其他的情況也會是一句話。例如：if/else運算式、when運算式、巢狀運算式......等等，也都是一句話的意思喔！我們只要記得如果函數一開始第一個字就是return的話，那我們就可以省略了！”

### Unit

讓我們回到前面printBusTimeInfo的函數，我們並沒有指定他的回傳值，這樣的函數是“沒有回傳值的”。BUT!!!當我們把滑鼠移到函數上的時候我們會發現提示的地方有個地方長的不太一樣：

<figure><img src="/files/lnX8mKLcEyT9iK4ekoc6" alt=""><figcaption></figcaption></figure>

在這裡我們發現提示的後面多了Unit這個東西，在函數裡面Unit的回傳值就代表這個函數“沒有回傳值”。當然Unit的用處並不是只有這樣而已，後面我們會介紹更多Unit會被用到的地方，而且也會探討為什麼Kotlin需要大費周章的創建Unit來代表“沒有”這個概念的理由。

## 函數多載

有些時候我們對於同樣的函數會有一些不同的需求，這個時候我們可以利用多載來實作不同需求的實作。比方說，我們看到Int裡面的plus函數：

<figure><img src="/files/UMdeKAet606eGHngYnps" alt=""><figcaption></figcaption></figure>

我們先不需要理會public跟operator，單純看fun後面的部分，我們會發現在plus這個函數可以傳入不同型態的東西，然後突出不一樣型態的東西，這就是函數多載的功用。我們在執行程式的時候，會依據我們傳入的引數來決定要使用哪一個函數實作。所以我們可以整理幾個函數多載的原則：

1. 函數名稱必須一樣
2. 函數的參數必須不同
3. 函數的回傳值可以不同

另外，如果我們在在實作的時候發現多載函數跟現有的函數傳入的參數只有部分不一樣的話，建議可以善用預設引數的功能，預設引數也是一種函數多載。

## 遞迴函數

在小時候的數學課我們有曾經學過遞迴函數，意思是使用函數的前項表示後項的函數。在程式語言裡面遞迴函數指的是會自己呼叫自己的函數，我們可以先來看看一個遞迴函數的範例：

```kotlin
fun printNumbers(number: Int) {
    if (number < 0) return
    printNumbers(number - 1)
    println(number)
}
```

這個範例可以印出0到number的所有的數字，我們可以看到函數區塊中在第二行呼叫了自己，但是把傳入的數值減1，這樣就可以依次把傳入的數值遞減。然後我們看到第一行有一個判斷式，這個判斷式是用來當作整個遞迴函數的中斷點，讓我們的遞迴函數不會一直無止盡的遞迴下去。在遞迴函數裡面中斷點“**非常的重要”**，如果沒有中斷點，函數就會一直呼叫自己，一直到程式沒辦法負荷拋出StackOverflowError為止，所以在寫遞迴函數的時候，要記得一定要記得寫上中斷條件。

<figure><img src="/files/5qz4yqZ8RNGTBj5X9atZ" alt=""><figcaption></figcaption></figure>
