Golang值方法和指针方法

1,504 阅读4分钟

image.png

概念

什么是方法?什么是函数?

Golang 中同时有函数和方法,方法是一个包含了接收者(receiver)的函数,receiver 可以是内置类型或者自定义类型struct的一个值或者是一个指针;函数是指不属于任何结构体、类型的方法,也就是说函数是没有接收者的。(可以将方法的receiver看做是函数的第一个argument)

// 函数
func main() {
	sum := add(1, 2)
	fmt.Println(sum)
}
 
func add(a, b int) int {
	return a + b
}
// 方法
type person struct {
	name string
}
 
func (p person) String() string{
	return "the person name is "+p.name
}

什么是值方法和指针方法?

  • 值方法:方法的接收者是值类型
  • 指针方法:方法的接收类型是指针类型
type T struct {
    Name string
}
// 值方法,方法接收者为值类型
func (t T) M1() {
    t.Name = "name1"
}
// 指针方法,方法接收者为指针类型
func (t *T) M2() {
    t.Name = "name2"
}

区别

这两种方法类型有什么区别吗?

  • 值方法(value methods)可以通过指针和值调用,
  • 指针方法(pointer methods)只能通过指针来调用,但是如果某个值是可寻址的(addressable,或者说左值),那么编译器会在值调用指针方法时自动插入取地址符,使得在此情形下看起来像指针方法也可以通过值来调用

如果方法接收者为对象的指针,则会修改原对象,如果方法接收者为对象的值,那么在方法中被操作的是原对象的副本,不会影响原对象

package main

import "fmt"

func main() {

   t1 := T{"t1"}

   fmt.Println("M1调用前:", t1.Name)
   t1.M1()
   fmt.Println("M1调用后:", t1.Name)

   fmt.Println("M2调用前:", t1.Name)
   t1.M2()
   fmt.Println("M2调用后:", t1.Name)

}

type T struct {
   Name string
}
// 方法实参和形参都是值类型,传递的是值的副本,所以不会对t1造成影响
func (t T) M1() {
   t.Name = "name1"
}
// 形参是指针类型,需要对实参进行自动取引用,传递的是t1的指针副本,所以修改后会对t1造成影响
func (t *T) M2() {
   t.Name = "name2"
}
// 结果
M1调用前: t1
M1调用后: t1
M2调用前: t1
M2调用后: name2
package main

import "fmt"

func main() {

   t1 := &T{"t1"}

   fmt.Println("M1调用前:", t1.Name)
   t1.M1()
   fmt.Println("M1调用后:", t1.Name)

   fmt.Println("M2调用前:", t1.Name)
   t1.M2()
   fmt.Println("M2调用后:", t1.Name)

}

type T struct {
   Name string
}
// 实参是指针类型,会自动解引用
func (t T) M1() {
   t.Name = "name1"
}
// 实参和形参都是指针类型
func (t *T) M2() {
   t.Name = "name2"
}
// 结果
M1调用前: t1
M1调用后: t1
M2调用前: t1
M2调用后: name2

取舍

值方法和指针方法两者如何取舍其实和另一个问题等价:就是你在定义函数时如何传参——是传值副本还是传指针副本

  • 如果方法需要修改receiver本身,那无疑需要传指针副本,也就是使用指针方法
  • 当值比较大的时候,值拷贝代价会变大,这时候也该考虑使用指针方法
  • 不可变对象等右值不能使用指针方法

扩展

go语言中的左值和右值

值类型区别情形
左值可寻址,可以通过&取地址符,获取内存地址
右值不可寻址,没有分配内存地址1.常量在编译期确定不会分配内存 2.函数返回值不分配内存 3.切片

关于go编译器自动解引用

  • 指针类型的实参调用形参为值类型的方法(会进行“自动解引用”)
  • 值类型的实参调用形参为指针类型的方法(会进行“自动取引用”)

总结

  • 当传递给方法的形参是一个值类型时(值类型接收器),原值的内容不会受到影响,因为值传递,传递进来的是原值的复制副本(形参是副本);相反,当方法的形参是一个指针类型时,原值的内容将可能受到影响,因为传递的是指针类型副本,虽然是副本但是指针中存储的地址仍然是实参的真实地址。
  • 方法接收者类型总结
    • 方法接收者是值,对象的值和指针均可以调用该方法;
    • 方法接收者是指针,只能通过指针或addressable value调用;
    • 当方法的接收者是值时,不管是值调用还是指针调用,方法内部都是对原对象的副本进行操作,不会影响原对象;
    • 当方法的接收者是指针时,不管是值调用还是指针调用,方法内部都是通过指针对原对象进行操作,会影响原对象。