iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
自我挑戰組

Go in 3o系列 第 9

[Day09] Go in 30 - 替自訂型別(custom types)加上方法(method)

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20230924/20162693PxAXVVr1y8.jpg

(圖片來源:CyberPanda twitter)

一、本篇重點

  • 替自訂型別(custom types)加上方法(method)
  1. 說明值接收器(Value Receiver) vs. 指標接收器(Pointer Receiver)差別
  2. 值接收器(Value Receiver)、指標接收器(Pointer Receiver),自動轉換

二、替自訂型別(custom types)加上方法(method)

從前面幾篇認識了結構,結構可以包含不同的欄位(或習慣說屬性),但我們能不能替他加一些功能呢,讓他有自己的函示可以呼叫 ?

是的,結構體在 Go 中可以包含方法,使其具備自己的功能。這些方法可以與結構體關聯,用於執行特定的操作或功能。方法是結構體的一部分,可以存取結構體的欄位並修改其狀態。

儘管** Go 不是物件導向語言**,並且沒有類(Class)的概念,但我們確實可以在函式定義時將它們關聯到特定型別的變數,通常是結構體變數。

這些與結構體"物件"綁定(bind)的函式通常被稱為結構的方法。方法使得我們可以將行為(方法)與數據(結構體)組合在一起,提高了程式的可讀性和組織性。

2.1 值接收器 vs 指標接受器

為了定義型別方法,有值接收器、指標接受器兩種寫法,首先值接收器如下:

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

指標接收器:

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

只要有接收器,我們就能用**變數.函式()**的語法去調用它。
接收器會讓結構變數以參數形式傳給函式,使我們能從函式中存取特定結構的欄位和其他方法。

不同型別(自訂型別或是結構型別)可以定義名稱一模一樣,但參數與回傳值完全不同的方法

2.2 接收器變數的型別限制

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

  • 不能是核心型別(如:int, string)、介面(interface)型別或指標型別
  • 方法必須與該型別必須定義在同一套件中

範例 :

package main

import (
	"fmt"
)

type name string //name 型別

type point struct { // point結構型別
	x int
	y int
}

func (n name) printName() { //name 型別方法(值接收器)
	fmt.Println("name:", n)
}

func (p *point) setPoint(x, y int) { //point 結構方法 (指標接收器)
	p.x = x
	p.y = y
}

func (p point) getPoint() string { //point 結構方法 (值接收器)
	return fmt.Sprintf("(%v,%v)", p.x, p.y)
}

func main() {
	var n name = "Golang"
	n.printName() //呼叫n的方法
	a, b := point{}, point{} //point{} 表示建立一個新的 point 結構的實例,這是一個結構類型的值。
	a.setPoint(10, 10)                   //呼叫a的setPoint()用法
	b.setPoint(10, 5)                    //呼叫b的setPoint()用法
	fmt.Println("point1:", a.getPoint()) //呼叫a的getPoint()用法
	fmt.Println("point2:", b.getPoint()) //呼叫b的getPoint()用法
}

輸出結果 :

name: Golang
point1: (10,10)
point2: (10,5)

2.3 值接收器與指標接收器差別

  • (p point)是值接收器 : 它會複製變數到p並傳給函式。函式可以讀取到原始變數的欄位,但若函式修改p,其變動不會反映在原變數身上。

  • (p *point)是指標接收器 : 它會把p轉成指標&p傳給函式。函式對p的任何修改,都會反映在原變數上。

舉例 : 我們將上節程式將指標接收器改為值接收器

func (p point) setPoint(x, y int) { //point 結構方法 (指標接收器)
	p.x = x
	p.y = y
}

更改完後 :

package main

import (
	"fmt"
)

type name string //name 型別

type point struct { // point結構型別
	x int
	y int
}

func (n name) printName() { //name 型別方法(值接收器)
	fmt.Println("name:", n)
}

func (p point) setPoint(x, y int) { //point 結構方法 (指標接收器)
	p.x = x
	p.y = y
}

func (p point) getPoint() string { //point 結構方法 (值接收器)
	return fmt.Sprintf("(%v,%v)", p.x, p.y)
}

func main() {
	var n name = "Golang"
	n.printName() //呼叫n的方法
	a, b := point{}, point{} //point{} 表示建立一個新的 point 結構的實例,這是一個結構類型的值。
	a.setPoint(10, 10)                   //呼叫a的setPoint()用法
	b.setPoint(10, 5)                    //呼叫b的setPoint()用法
	fmt.Println("point1:", a.getPoint()) //呼叫a的getPoint()用法
	fmt.Println("point2:", b.getPoint()) //呼叫b的getPoint()用法
}

輸出結果:

name: Golang
point1: (0,0)
point2: (0,0)

在範例中,當 setPoint 方法使用值接收器 (p point) 時,它只修改了方法接收到的結構體的副本,所以原始結構體的欄位沒有被修改。 而在 getPoint 方法中,它也使用了值接收器,因此它只會傳回原始結構體的欄位的值,而不會修改它們。
換句話說,改成使用值接收器後,setPoint()只修改了它收到的復本,所以main()內的a和b結構欄位仍然維持在零值。

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

Go在將值傳入指標接收器時,會自行將他轉成指標傳入。這其實是個雙向機制,這裡我們會檢視它如何運作。

修改上一節程式:

package main

import (
	"fmt"
)

type point struct { // point結構型別
	x int
	y int
}

func (p *point) setPoint(x, y int) { //point 結構方法 (指標接收器)
	p.x = x
	p.y = y
}

func (p point) getPoint() string { //point 結構方法 (值接收器)
	return fmt.Sprintf("(%v,%v)", p.x, p.y)
}

func main() {
	a := point{}  //a是結構值
	b := &point{} //b是指向結構變數的指標
	a.setPoint(10, 10)                   //呼叫a的setPoint()用法
	b.setPoint(10, 5)                    //呼叫b的setPoint()用法
	fmt.Println("point1:", a.getPoint()) //呼叫a的getPoint()用法
	fmt.Println("point2:", b.getPoint()) //呼叫b的getPoint()用法
}

注意 b := &point{} ,b 雖然是指標,但是呼叫其方法時不需要寫成像是(*b).setPoint()。這是因為Go語言做了自動解除參照

這是因為 Go 語言會自動解引用指針,以便讓你更方便地呼叫方法。 這個語法糖使程式碼更清晰和簡潔。 所以,當你有一個指向類型的指標 ptr,你可以像這樣呼叫方法:

ptr.someMethod()

而不需要寫成 (*ptr).someMethod()。
這種自動解引用機制讓 Go 程式碼更易讀、更易寫,減少了不必要的冗餘程式碼。

輸出結果 :

point1: (10,10)
point2: (10,5)

可以看到 b 即使變成指標,'它在使用方法setPoint()(指標接收器)和getPoint()(值接收器)仍舊正常,當我們呼叫a.setPoint()時,a會自動變成指標,這是Go語言在背後做的以下自動處理 :

  1. 呼叫 a.setPoint()時,a是一個值,setPoint()卻使用指標接收器,因此實際上Go將其轉換成指標後會是(&a).setPoint()。
  2. 呼叫b.getPoint()時,b是一個指標,getPoint()卻使用了值接收器,Go會自動將它轉成(*b).getPoint();

也因自動轉換,我們就不需要自行轉換值或是指標,就能直接呼叫型別的任何方法 !!

以上就是本篇內容~~


上一篇
[Day08] Go in 30 - 比較結構與內嵌結構
下一篇
[Day10] Go in 30 - 空介面與型別檢查
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言