iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
自我挑戰組

Go in 3o系列 第 13

[Day13] Go in 30 - 函式 - 以函式為型別的參數

  • 分享至 

  • xImage
  •  

一、本篇提要

本篇會介紹如何將函式當成引數,傳遞給其他函式;函式也可以傳回函式,甚至可以拿函式賦值給變數(如閉包);除此之外會說明defer,延後函式的執行時機。

  • 使用自訂函式型別的參數
  • 用自訂函式型別作為回傳值
  • defer

一、使用自訂函式型別的參數

自訂函式型別用途範例 :

package main

import "fmt"

type calc func(int, int) string // 自訂函式型別

func main() {
    calculator(add, 5, 6)
}

func add(i, j int) string {
    result := i + j
    return fmt.Sprintf("%d + %d = %d", i, j, result)
}

//接收自訂函式型別參數 f
//效果等同寫 f func(int, int) string
func calculator(f calc, i, j int) {
    fmt.Println(f(i, j))//呼叫傳入函式
}

結果:
https://ithelp.ithome.com.tw/upload/images/20230928/20162693qjqnbrro0V.png

由上可以看到,add(i, j int) 以及 func(int, int)的signature是一樣的,因此add()可被視為calc型別,而函式calculator接受一個calc 型別參數,因此我們可以將add傳給他**(只填入名稱,不帶小括弧)**

我們仔細說明上述範例程式:

type calc func(int, int) string // 自訂函式型別

表示calc是一個函是型別,它的特徵包括兩個int參數、一個string回傳值,只要任何函是符合此特徵,他就是calc。

func add(i, j) string

如上述,他符合calc特徵,所以他是calc型別。

func calculator(f calc, j int)

calculator函式的參數f 為 calc 型別,而add()如上所說符合calc 型態,因此可以作為傳入f的引數。

範例二 :

package main

import "fmt"

type salaryfunc func(int, int) int // 自訂函式型別

func main() {
	devSalary := salary(50, 2080, developerSalary)
	bossSalary := salary(15000, 25000, managerSalary)

	fmt.Printf("經理薪資 : %d\n", bossSalary)
	fmt.Printf("開發者薪資 : %d\n", devSalary)
}

func salary(x, y int, f salaryfunc) int {
	pay := f(x, y)
	return pay
}

func managerSalary(bossSalary, bonus int) int {
	return bossSalary + bonus
}

func developerSalary(hourlyRate, hoursWorked int) int {
	return hourlyRate * hoursWorked
}

以這個範例來說,我們以salary()這個函式簡化了程式碼,將需要額外計算的developerSalary新建一個函式,且特徵符合salaryFunc,就可以當作引數傳入,這樣就不用去調整salary()本身。

三、用自訂函式型別作為傳回值

我們不只能在函是參數使用自訂函式型別,也可以將它當成傳回值型別,如以下範例:

package main

import "fmt"

type salaryfunc func(int, int) int // 自訂函式型別

func main() {

	add := calculator("+")
	subtract := calculator("-")

    fmt.Println(add(5, 6))
    fmt.Println(subtract(10, 5))

    fmt.Println("add() 型別: %T\n", add)
    fmt.Println("subtract() 型別: %T\n", subtract)

}

func calculator(operator string) func(int, int) int {
	//根據使用者的引數傳回對應的函式
	switch operator {
	case "+":
		return func(i, j int) int {
			return i + j
		}
	case "-":
		return func(i, j int) int {
			return i - j
		}
	}
	return nil
}

結果 :

https://ithelp.ithome.com.tw/upload/images/20230928/20162693S7d4GZuzY7.png

四、defer

在本小節將介紹defer,看看如何改變函式執行的時機點。
defer敘述能延後函式執行時機,使該函式等到父函式結束(跑完所有程式碼或執行return)的前一刻,才會被執行。

簡單說,在呼叫某個函式時加上defer,他不會當場執行,而是變成它所在的父函式裡最後一個執行的東西。
來看例子吧 :

package main

import "fmt"


func main() {

    defer done()
    fmt.Println("main() 開始")
    fmt.Println("main() 結束")
}

func done() {
    fmt.Println("換我結束!")
}

結果:

https://ithelp.ithome.com.tw/upload/images/20230928/20162693NKQAEduouD.png

這樣就比較好了解了八,加了defer就會延後done()執行時機。

defer 的使用時機

defer 通常是用來善後的,包括像是釋放資源、關閉以開啟的的檔案、關閉DB連線、移除程式先前建立的設定/站存檔,除此之外,defer還可以用來從程式的錯誤狀況復原,下一個主題就會談到這部分

同理,defer 不僅具名函式可用而且匿名函式也可以!!

func main() {
    defer func() {
        fmt.Println("換我結束")
    }()
    
    fmt.Println("main()開始")
    fmt.Println("main()結束")
}

4.1 多重defer的執行順序

我們也可以在同一個函式內使用多個defer敘述來延後多個函式,但這個函式被延後的順序,是遵循先進後出FILO(First In Last Out),先進後出的概念簡而言之就是先進去的會後出來(廢話),可以想像成疊盤子,當盤子從第一個越疊越高,當我們要取會第一個盤子時,一定是從最高的開始取,所以第一個盤子進去,會最後一個出來。

來看個例子 :

package main

import "fmt"


func main() {

    defer func() {
        fmt.Println("我是第1個宣告出來的")
    }()
    defer func() {
        fmt.Println("我是第2個宣告出來的")
    }()
    defer func() {
        fmt.Println("我是第3個宣告出來的")
    }()
    defer func() {
        fmt.Println("我是第4個宣告出來的")
    }()
    defer func() {
        fmt.Println("我是第5個宣告出來的")
    }()

    f1 := func() {
        fmt.Println("f1結束")
    }
    f2 := func() {
        fmt.Println("f2結束")
    }
   
    f1()
    f2()
    fmt.Println("main() 結束!")
}

結果如下 :

https://ithelp.ithome.com.tw/upload/images/20230928/20162693ioGaoUJhIM.png

4.2 defer 對變數值的副作用

使用defer敘述時請務必審慎,其中一個必須考慮到的是,如果defer函式有使用到外部變數,它執行時會發生什麼結果

package main

import "fmt"


func main() {

    age := 25
    name := "John"
    defer personAge(name, age)

    age *= 2
    fmt.Println("年齡加倍:")
    personAge(name, age)

}

func personAge (name string, i int) {
    fmt.Printf("%s 是 %d 歲\n", name, i)
}

輸出結果 :
https://ithelp.ithome.com.tw/upload/images/20230928/20162693gTlKy9rXg0.png

對此範例,我們可以得知,這個defer只會取得變數再傳遞的那一刻當下的值,就算是變數在之後有所變動,等到defer函式實際執行時,他看到的變數值也不會反映外圍函式中的變動。

以上幾張就是Go語言函式的整理與介紹,大概可以知道如何定義和呼叫函式、各種函式類型、閉包以及本篇的defer延遲函式,接下來將進入到Go語言中error值及錯誤型別。


上一篇
[Day13] Go in 30 - 函式 - 參數不定函式、匿名函式與閉包
下一篇
[Day14] Go in 30 - 錯誤處理 -認識標準函式庫error騷兩圈
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言