iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
Modern Web

起步Go!Let's Go!系列 第 10

[ Day 10] Go 函式中的魔法 Return

  • 分享至 

  • xImage
  •  

在 Go 中,return 用於結束一個函式的執行並返回一個值(或多個值)。
在進入 return 前,我們簡單複習一下昨天的函示。

函式的目的在於包裝一段程式碼,讓他可以被重複利用。
而函式會經歷定義函式跟呼叫函式這兩個運用流程。

結束函式

要怎麼結束函式?就是用前一章有提過的 return
基本語法:

return

這樣就可以結束函式了。

package main
import "fmt"
func showHello(){
    fmt.Println("Hello")
    fmt.Println("你好")
}
func main(){
    showHello()
}

執行結果:
Hello
你好

main 函式呼叫 showHello 函式,印出 Hello 你好,自然結束,跳回 main 函式。
當沒有寫 return 程式就會一步一步執行下去直到結束。

加 return

package main
import "fmt"
func showHello() {
	fmt.Println("執行 return !!!")
	return
	fmt.Println("Hello")
	fmt.Println("你好")
}
func main() {
	showHello()
}

執行結果:
執行 return !!!
當 main 函式呼叫 showHello 函式,碰到 return 程式就會中斷跳回 main 函式,所以不會印出任何東西。
而且當你在編譯器寫這段程式時,會出現 unreachable code 這個意思是指到達不了的程式,這是因為用了 return 的關係,所以 Hello你好 都不會印出來。

範例一

  • 搭配參數及判斷:
package main
import "fmt"
func show (msg string){
    if msg == "Hello"{
        return
    }
    fmt.Println(msg)
}
func main(){
    show("Hello")
    show("你好")
}

執行結果:
你好

以上面的程式來看,就只會印出你好

函式回傳值

昨天的章節有提到,函式不一定要有回傳值,但有時回傳值會非常好用。
基本語法:

func 函式名稱(參數列表)回傳值資料型態{
    函式內部的程式碼
    return 回傳值,須符合定義中的資料型態
}

要注意的一點,回傳值必須與回傳值資料型態相等。

package main
import "fmt"

// 可以做整數乘法的函式
func multiply(n1 int, n2 int) int{
    var result int = n1 * n2
    return result
}
// 呼叫函式,取得回傳值
func main (){
    var x int = multiply(3, 4){
        fmt,Println(x)
    }
}

執行結果:
12

根據上面程式碼,解釋如下:

  1. main 函式會去呼叫 multiply 函式並將引數帶入到 multiply 函式的參數內。
  2. 經過 multiply 函式計算完的 result,會因為 return 而回傳到 main 函式中呼叫它的地方,也就是 x
  3. 所以這時後 x 變數就會是剛剛 multiply 函式計算過後的結果。
  4. 並將結果印出來。

多個回傳值

在 Go 中,支援一次定義多個回傳值,也可以用來快速地實作錯誤處理。
而定義好幾個回傳值的資料型態要用逗號隔開,一個蘿蔔一個坑。
同時,多個回傳值非常有用,特別是在處理錯誤時,它允許你返回一個正常的結果以及一個錯誤值,而不需要使用異常捕獲或其他複雜的錯誤處理機制。
基本語法:

func 函式名稱(參數列表)(資料型態,資料行態,.....){
    函式內部的程式碼
    return 回傳值, 回傳值, .....
}

範例一:

package main
import "fmt"

func calc (a int, b int)(int, int){
    return a+b, a-b
}
func main(){
    sum,diff := calc(5, 2)
    fmt.Println(sum)
    fmt.Println(diff)
}

執行結果:
7
3

補充:
這邊有一個關於宣告變數的新用法,可以在 := 左邊放接收回傳值的變數,在這邊就是 sum, diff,接著,用 , 分開,就可以得到我們要的值。

Go 允許用一個等式一次宣告多個變數,範例如下:

package main
import "fmt"
func main(){
    a, b := 3, 7
    fmt.Println(a, b)
}

執行結果:
3 7

範例二:

package main

import (
    "fmt"
    "errors"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    result := a / b
    return result, nil
}

func main() {
    quotient1, err := divide(10.0, 2.0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", quotient1)
    }

    quotient2, err = divide(10.0, 0.0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", quotient2)
    }
}

執行結果:
Result: 5
Error: division by zero

根據上面程式碼,解釋如下:

  1. main 函式會去呼叫 divide 函式並將引數分別帶入到函式的參數內。
  2. b 不等於 0 時,會將 ab 相除的 result,以及身為 error 要回傳的值 nil,一起回傳給 main()
  3. b 等於 0 時,會因為if 判斷式的關係而 return (回傳) 給 main 函式 0 以及 division by zero
  4. 所以這時執行的結果才會是 Result: 5Error: division by zero

兩個太多,我只要一個

之前有提到 Go 宣告變數,該變數一定要使用到,不然就會報錯。
但是有時只要一個變數值,但又有兩個回傳值,這時該怎麼解決?
只要將不使用的值的變數設為 _ 即可。

package main
import "fmt"
func calc(a int, b int) (int, int){
    return a+b, a-b
}
func main(){
    sum, _ := calc(5, 2)
    fmt.Println(sum)
}

執行結果:
7

練習

1. 單一回傳值

package main
import "fmt"
func add(n1 int, n2 int) int {
    var result int = n1 + n2
    fmt.Println(result)
    return 10
}
func main(){
    add(3, 4)
}

執行結果:
7

這時候就只會印出 7。
10 跑去哪了?
如果要印出 10,就要給一個變數讓它存放 return 的值。

package main
import "fmt"
func add(n1 int, n2 int) int {
    var result int = n1 + n2
    fmt.Println(result)
    return 10
}
func main(){
    var x int = add(3, 4)
    fmt.Println(x)
}

執行結果:
7
10

讓我們回傳相加的結果:

package main
import "fmt"
func add(n1 int, n2 int) int {
    var result int = n1 + n2
    return result  // 回傳 n1 n2 相加的結果
}
func main(){
    var x int = add(3, 4)
    fmt.Println(x)
}

執行結果:
7

那為什麼要回傳值?
說不定我們需要用到回傳值繼續做接下來的運算或操作。
所以利用剛剛的回傳值來繼續運算,如下:

package main
import "fmt"
func add(n1 int, n2 int) int {
    var result int = n1 + n2
    return result
}
func main(){
    var x int = add(3, 4)
    fmt.Println(x*10)
}

執行結果:
70

2. 多個回傳值

package main
import "fmt"
func test() (int, string) {
    return 5, "Hello"
}
func main(){
    var x int
    var s string
    x, s = test()
    fmt.Println(x, s)
}

執行結果:
5 Hello

記住!!!!!
在定義回傳值的資料型態時,給 int 就要是 int,一個蘿蔔一個坑,不然會出錯。

回傳值命名

Go 也可以為回傳值命名,這樣不僅增加了程式碼的可讀性也使函式更易於理解。

package main
import "fmt"

func calc (a int, b int)(sum int, diff int){
    sum, diff = a+b, a-b
    return
}
func main(){
    sum, diff := calc(5, 2)
    fmt.Println(sum)
    fmt.Println(diff)
}

執行結果:
7
3

這樣寫也是可以:

package main
import "fmt"

func calc (a int, b int)(sum int, diff int){
    sum, diff = a+b, a-b
    return sum, diff
}
func main(){
    sum, diff := calc(5, 2)
    fmt.Println(sum)
    fmt.Println(diff)
}

執行的結果都會是相同的。

執行結果:
7
3

注意!!!

關於 Return 有幾點需要注意:

1. return 後的程式不會執行; 有回傳值的函示不一定要去接收回傳值。

package main
import "fmt"
func test(num int) int{
    if num > 10 {
        fmt.Println("num > 10")
        return num
    }
    fmt.Println("num < 10")
    return num
}

func main(){
    test(100)
}

執行結果:
num > 10

當執行到第 6 行時,因觸發 return,所以後面的程式就不會執行,所以就會跳回呼叫該函式的地方,也就是第 12 行。
另外,有回傳值的函式並沒有強制一定要去接收它。

2. 函式中宣告的變數有所謂的scope

Loop 有提到 scope 在迴圈內所宣告的變數並不可以在迴圈外使用,將這個概念套到函式上。

package main
import "fmt"
func test(num int){
    fmt.Println("in test()", num)
}

func main(){
    num := 10
    test(100)
    fmt.Println("in main()", num)
}

執行結果:
in test() 100
in main() 10

這兩個函式都有用到 num 這個變數,但是因為 scope 的關係,所以這兩個函式所宣告的變數完全不受到影響,所以在使用時只要顧慮該函式的 scope 即可。

3. 有回傳值型態的函示,在函式的最後一定要使用 retrun

在 Go 中,如果一個有回傳值的函式有明確的回傳型態,則必須在函式的最後使用 return 來返回一個該型態的值,否則會發生編譯錯誤。

package main
import "fmt"
func test(num int) int{
    if num > 10 {
        fmt.Println("num > 10")
        return num
    }
    fmt.Println("num < 10")
}

func main(){
    test(100)
}

執行結果:
missing return (exit status 1)

package main
import "fmt"
func test(num int) int{
    if num > 10 {
        fmt.Println("num > 10")
        return num
    }
    fmt.Println("num < 10")
    return
    fmt.Println("after return")
}

func main(){
    test(100)
}

執行結果:
missing return (exit status 1)

根據這兩個例子,可以看到當 return 沒有在最後,就會發生編譯錯誤,所以當這樣修正過後。

package main
import "fmt"
func test(num int) int{
    if num > 10 {
        fmt.Println("num > 10")
        return num
    }
    fmt.Println("num < 10")
    fmt.Println("before return")
    return
}

func main(){
    test(5)
}

執行結果:
num < 10
before return

4. 函式擺放順序不重要

在 Go 中,函式的擺放順序對於程式的正確性是沒有影響的。這是因為 Go 在編譯時會將整個程式的所有函式都收集起來,並且進行符號解析,然後再進行編譯。
即使在程式中先呼叫了一個還未定義的函式,只要這個函式在後面定義了,程式也是可以正常運行的。

scope 的例子改寫:

package main
import "fmt"
func main(){
    num := 10
    test(100)
    fmt.Println("in main()", num)
}

func test(num int){
    fmt.Println("in test()", num)
}

執行結果:
in test() 100
in main() 10

執行結果是一樣的。

今天關於 Return 講得蠻多的,以下重點整理一下:

Return 整理

  1. 使用函式時,只要觸發 return,那之後的程式就不會再繼續執行(上一章有提到)。
  2. 有回傳值的函示,不一定要去接收該函式的回傳值。
  3. 函式中宣告的變數有所謂的 scope,並不會影響其他函式中所宣告的變數。
  4. 有回傳值型態的函式,在函式最後一定要用 return
  5. Go中,函式擺放順序不重要。

參考資料:

  1. Golang 函式回傳值 - 命令 return 的用法 By 彭彭

上一篇
[ Day 09] Go 函式魔法 - 定義、召喚、傳遞
下一篇
[ Day 11] Go 指標與記憶體魔法
系列文
起步Go!Let's Go!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言