- [定義]: golang的方法(Method)是一個帶有receiver的函式Function,Receiver是一個特定的struct型別,當你將函式Function附加到該receiver, 這個方法Method就能獲取該receiver的屬性和其他方法。
- [物件導向]: golang方法Method允許你在型別上定義函式,是一個物件導向的行為程式碼, 這也有一些益處:同一個package可以有相同的方法名, 但是函式Function卻不行。
func (receiver receiver_type) some_func_name(arguments) return_values
從應用上講,方法接受者分為值接收者,指標接收者,初級golang學者可能看過這兩個接收者實際表現, 但是一直很混淆,很難記憶。
本次我們使用地址空間的角度來剖析實質,強化記憶。
值型別方法接收者
值接受者: receiver是struct等值型別。
下面定義了值型別接受者Person
, 嘗試使用Person{}
, &Person{}
去呼叫接受者函式。
package main
import "fmt"
type Person struct {
name string
age int
}
func (p Person) say() {
fmt.Printf("I (%p) ma %s, %d years old \n",&p, p.name,p.age)
}
func (p Person) older(){ // 值型別方法接受者: 接受者是原型別值的副本
p.age = p.age +1
fmt.Printf("I (%p) am %s, %d years old\n", &p, p.name,p.age)
}
func main() {
p1 := Person{name: "zhangsan", age: 20}
p1.older()
p1.say()
fmt.Printf("I (%p) am %s, %d years old\n",&p1, p1.name,p1.age)
p2 := &Person{ name: "sili", age: 20}
p2.older() // 即使定義的是值型別接受者, 指標型別依舊可以使用,但我們傳遞進去的還是值型別的副本
p2.say()
fmt.Printf("I (%p) am %s, %d years old\n",p2, p2.name,p2.age)
}
嘗試改變p1=Person{},p2=&Person{}的欄位值:
I (0xc000098078) am zhangsan, 21 years old
I (0xc000098090) ma zhangsan, 20 years old
I (0xc000098060) am zhangsan, 20 years old
I (0xc0000980c0) am sili, 21 years old
I (0xc0000980d8) ma sili, 20 years old
I (0xc0000980a8) am sili, 20 years old
p1=Person{} 未能修改原p1的欄位值; p2=&Person{}也未能修改原p2的欄位值。
- 通過Person{}值去呼叫函式, 傳入函式的是原值的副本, 這裡通過第一行和第三行的
%p
印證 (%p:輸出地址值, 這兩個非同一地址)。 - 即使定義的是值型別接收者,指標型別依舊可以呼叫函式, 但是傳遞進去的還是值型別的副本。
帶來的效果是:對值型別接收者內的欄位操作,並不影響原呼叫者。
指標型別接受者
方法接收者也可以定義在指標上,任何嘗試對指標接收者的修改,會體現到呼叫者。
package main
import "fmt"
type Person struct{
name string
age int
}
func (p Person) say(){
fmt.Printf("I (%p) am %s, %d years old\n", &p, p.name,p.age)
}
func (p *Person) older(){ // 指標接受者,傳遞函式內部的是原型別值(指標), 函式內的操作會體現到原指標指向的空間
p.age = p.age +1
fmt.Printf("I (%p) am %s, %d years old\n", p, p.name,p.age)
}
func main() {
p1 := Person{"zhangsan",20}
p1.older() // 雖然定義的是指標接受者,但是值型別依舊可以使用,但是會隱式傳入指標值
p1.say()
fmt.Printf("I (%p) am %s, %d years old\n", &p1, p1.name,p1.age)
p2:= &Person{"sili",20}
p2.older()
p2.say()
fmt.Printf("I (%p) am %s, %d years old\n", p2, p2.name,p2.age)
}
嘗試改變p1= Person{}, p2=&Person{}欄位值
I (0xc000098060) am zhangsan, 21 years old
I (0xc000098078) am zhangsan, 21 years old
I (0xc000098060) am zhangsan, 21 years old
I (0xc000098090) am sili, 21 years old
I (0xc0000980a8) am sili, 21 years old
I (0xc000098090) am sili, 21 years old
p1=Person{} 成功修改欄位值,p2=&Person{}也成功修改欄位值。
- 通過p1也可以呼叫指標函式接收者, 但是實際會隱式傳遞指標值。
- 指標接收者,入參是原指標值,函式內的操作會體現到原呼叫者。
帶來的效果: 任何對指標接收者的修改會體現到 原呼叫者。
什麼時候使用指標接收者
- 需要對接受者的變更能體現到原呼叫者
- 當struct佔用很大記憶體,最好使用指標接受者,否則每次呼叫接受者函式 都會形成struct的大副本
golang方法的幾種姿勢
接上例子:
- 將接收者函式當擴充套件函式
Person.say(p1)
(*Person).older(p2)
依舊是 值型別/指標型別方法接收者的效果。
I (0xc0000040d8) am zhangsan, 21 years old
I (0xc0000040a8) am sili, 22 years old
這種姿勢相對於物件導向的接收者不常見。
- golang 方法鏈條
func (p Person) printName() Person{
fmt.Printf("Name:%s", p.Name)
return p
}
- Non_struct型別golang方法
type myFloat float64
func (m myFloat) ceil() float64 {
return math.Ceil(float64(m))
}
以上是有態度的馬甲記錄的有關golang 方法接收者的全部用法,通過%p
,我們探究了值型別/指標接收者的呼叫原理。