golang 方法接收者

  • [定義]: 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,我們探究了值類型/指針接收者的調用原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章