(圖片來源:CyberPanda twitter)
從前面幾篇認識了結構,結構可以包含不同的欄位(或習慣說屬性),但我們能不能替他加一些功能呢,讓他有自己的函示可以呼叫 ?
是的,結構體在 Go 中可以包含方法,使其具備自己的功能。這些方法可以與結構體關聯,用於執行特定的操作或功能。方法是結構體的一部分,可以存取結構體的欄位並修改其狀態。
儘管** Go 不是物件導向語言**,並且沒有類(Class)的概念,但我們確實可以在函式定義時將它們關聯到特定型別的變數,通常是結構體變數。
這些與結構體"物件"綁定(bind)的函式通常被稱為結構的方法。方法使得我們可以將行為(方法)與數據(結構體)組合在一起,提高了程式的可讀性和組織性。
為了定義型別方法,有值接收器、指標接受器兩種寫法,首先值接收器如下:
func(<接收器變數> <型別>) 函式名稱(){
// ...
}
指標接收器:
func(<接收器變數> <*型別>) 函式名稱(){
// ...
}
只要有接收器,我們就能用**變數.函式()**的語法去調用它。
接收器會讓結構變數以參數形式傳給函式,使我們能從函式中存取特定結構的欄位和其他方法。
不同型別(自訂型別或是結構型別)可以定義名稱一模一樣,但參數與回傳值完全不同的方法
接收器變數的型別有以下限制:
範例 :
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)
(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結構欄位仍然維持在零值。
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語言在背後做的以下自動處理 :
也因自動轉換,我們就不需要自行轉換值或是指標,就能直接呼叫型別的任何方法 !!
以上就是本篇內容~~