iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0
自我挑戰組

跟著 Go 實戰聖經 一起自學 Go系列 第 21

DAY 21 Go 語言 幫自訂型別加上自己的函式或方法(method)

  • 分享至 

  • xImage
  •  

昨天介紹完內嵌函式,今天繼續將結構的最後小部分完整介紹完!

幫自訂型別加上自己的函式或方法(method)

在昨天一開始我們便有說到可以幫自訂型別加上自訂的函式或方法,其時我們可以在定義函式時,將其指向特定型別的變數,這些綁定結構物件的函式,通常我們將其稱為 方法(method) ,而定義型別方法有以下兩種寫法 值接收器(value receiver)、指標接收器(point receiver)。

值接收器:

func (<接收器變數> <型別>) 函式名稱() {
    // func 內容
}

指標接收器:

func (<接收器變數> <*型別>) 函式名稱() {
    // func 內容
}

無論函式有以上哪兩種接收器,接收器會讓結構變數以參數的形式傳遞,所以只要有其中一種接收器,我們便可用 變數.函式() 來呼叫函式。

但接收器變數有以下兩種型別的限制:

  1. 不能是核心型別 (string, bool) 、介面型別 (interface) 或指標型別
  2. 方法(method)與該型別必須定義在同一個套件中。

簡單來說就是只能在 自己套件內 定義的自訂型別或結構綁上方法,如:你在結構 A 內嵌結構 B ,那就能透過結構 A 呼叫結構 B 裡的方法。

補充:
不同型別可以定義相同的名稱但參數回傳值不同的方法。
範例 5:

package main

import "fmt"

type id int  // 自訂一個名為 name 的字串型別
type hunter struct { // 定義名為 hunter 的結構型別
    role string
    ability string
}

func (i id) printID() { // 值接收器寫法, i 為接收器變數, id 是結構型別方法
    fmt.Println("id:", i)  // 印出 id
}

func (h *hunter) setHunterPoint(role, ability string) { // 指標接收器寫法, h 為接收器變數, hunter 是結構方法
    h.role = role  // 存取結構欄位
    h.ability = ability
}

func (h hunter) getHunterPoint() string {  // 值接收器寫法, h 為接收器變數, hunter 是結構型別方法
    return fmt.Sprintf("(%v, %v)", h.role, h.ability)
}

func main() {
    var i id = 1  // 定義 i 變數,且賦值
    i.printID()  // 呼叫 i 的方法 printID()

    a, b := hunter{}, hunter{}
    a.setHunterPoint("奇犽", "變化系")  // 呼叫 a 的 setHunterPoint 方法,且帶入參數
    b.setHunterPoint("小傑", "強化系")  // 呼叫 b 的 setHunterPoint 方法,且帶入參數
    fmt.Println("hunter1:", a.getHunterPoint())  // 呼叫 a 的 getHunterPoint 方法
    fmt.Println("hunter2:", b.getHunterPoint())  // 呼叫 b 的 getHunterPoint 方法
}

範例 5(執行結果):

id: 1
hunter1: (奇犽, 變化系)
hunter2: (小傑, 強化系)

看到這邊我滿頭問號, h *hunterh hunter 到底差在哪?我是知道在型別前加上 * 就可以宣告一個指標變數,所以有加 * 的是指標接收器,但你要跟我說為什麼以及兩者的差距呀~

h hunter 值接收器:
值接收器會複製變數 h ,並將變數 h 傳給後面的 getHunterPoint() 函式,而函式可以讀取到原變數 h 的欄位,但如果我們在函式內修改 h 的值,也不會影響到原本的變數上(因為前面有說他是複製出來的 h )。

h *hunter 指標接收器:
指標接收器會將 h 轉成指標 &h 傳進後面的 setHunterPoint() 函式,阿因為指標只是一個手指頭指向,所以若我們在函式內對 h 做修改,是會影響到本來的 h 呦!

我們把剛剛範例 5 稍做修改,將原本的指標接收器,改為值接收器,看看會發生什麼事吧!

範例 6:

func (h hunter) setHunterPoint(role, ability string) { // 指標接收器寫法, h 為接收器變數, hunter 是結構方法
    h.role = role  // 存取結構欄位
    h.ability = ability
}

範例 6(執行結果):

id: 1
hunter1: (, )
hunter2: (, )

執行的結果出來都是零值(空字串)的原因是因為,函式收到**複製 h ** ,也就是 原變數 h 的欄位 ,原本欄位因為沒有賦值他就會呈現零值(空字串)的樣子。

接收器值與指標的自動轉換

我們在使用指標接收器的時候,接收器會將 h 轉成指標 &h 傳進後面的 setHunterPoint() 函式,但其實反之亦然,他是可以雙向去做自動轉換的。
範例 7:

package main

import "fmt"

type hunter struct { // 定義名為 hunter 的結構型別
    role string
    ability string
}

func (h *hunter) setHunterPoint(role, ability string) { // 指標接收器寫法, h 為接收器變數, hunter 是結構方法
    h.role = role  // 存取結構欄位
    h.ability = ability
}

func (h hunter) getHunterPoint() string {
    return fmt.Sprintf("(%v, %v)", h.role, h.ability)
}

func main() {
    a := hunter{}
    b := &hunter{}
    a.setHunterPoint("奇犽", "變化系")
    b.setHunterPoint("小傑", "強化系")
    fmt.Println("hunter1:", a.getHunterPoint())
    fmt.Println("hunter2:", b.getHunterPoint())
}

範例 7(執行結果):

hunter1: (奇犽, 變化系)
hunter2: (小傑, 強化系)

補充:
在前面章節我們知道,若要解除指標的參照,需要在指標變數前加上 * ; 但同時也有說到,指向結構的屬性或函式, Go 語言會自動幫你解除參照,這就是為什麼上方範例 7 我們不用特別將其寫成 (*b).setHunterPoint("小傑", "強化系") ,因為 Go 語言會自動幫我們做轉換。

從範例 7 我們可以從以下兩個地方得知,接收器值與指標可以自動轉換:

  1. a 本來是一個值,呼叫 a.setHunterPoint("奇犽", "變化系") 時, setHunterPoint 使用指標接收器,代表 Go 語言自動幫我們將其轉換成 (&a).setHunterPoint()
  2. b 本來是一個指標, 呼叫 b.getHunterPoint() 時, getHunterPoint 使用值接收器,代表 Go 語言自動幫我們將其轉換成 (*b).getHunterPoint()

今天終於將結構 (struct) 介紹完畢,明天繼續來學習在 Go 語言中的介面及型別檢查,明天見~~


上一篇
DAY 20 Go 語言 內嵌結構 (embedding struct)
下一篇
DAY 22 Go 語言 型別檢查
系列文
跟著 Go 實戰聖經 一起自學 Go30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言