golang 方法接收者

部落格猿馬甲哥發表於2022-05-31
  • [定義]: 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也可以呼叫指標函式接收者, 但是實際會隱式傳遞指標值。
  • 指標接收者,入參是原指標值,函式內的操作會體現到原呼叫者

帶來的效果: 任何對指標接收者的修改會體現到 原呼叫者。

什麼時候使用指標接收者

  1. 需要對接受者的變更能體現到原呼叫者
  2. 當struct佔用很大記憶體,最好使用指標接受者,否則每次呼叫接受者函式 都會形成struct的大副本

golang方法的幾種姿勢

接上例子:

  1. 將接收者函式當擴充套件函式
	Person.say(p1)
	(*Person).older(p2)

依舊是 值型別/指標型別方法接收者的效果。

I (0xc0000040d8)  am zhangsan, 21 years old
I (0xc0000040a8)  am sili, 22 years old

這種姿勢相對於物件導向的接收者不常見。

  1. golang 方法鏈條
func (p Person) printName() Person{
  fmt.Printf("Name:%s", p.Name)
  return p
}
  1. Non_struct型別golang方法
type myFloat float64
func (m myFloat) ceil() float64 {
   return  math.Ceil(float64(m))
}

以上是有態度的馬甲記錄的有關golang 方法接收者的全部用法,通過%p,我們探究了值型別/指標接收者的呼叫原理。

相關文章