zoukankan      html  css  js  c++  java
  • golang指针接收者和值接收者方法调用笔记

    初学go时很多同学会把 值接收者 和 指针接收者 的方法相互调用搞混淆,好多同学都只记得指针类型可以调用值接收者方法指针接收者方法,而值类型只能调用值接收者方法,其实不然,在某些情况下,值类型也是可以调用指针接收者方法的。

    最近又看到有同学发出了这样的疑问,所以打算记录一下,用以备忘、分享。

    类型不同可以调用

    package main
    
    import (
    	"fmt"
    )
    
    type field struct {
    	name string
    }
    
    func (p *field) pointerMethod() {
    	fmt.Println(p.name)
    }
    
    func (p field) valueMethod() {
    	fmt.Println(p.name)
    }
    
    func main() {
    	fp := &field{name: "pointer"}
    	fv := field{name: "value"}
    
    	fp.pointerMethod()
    	fp.valueMethod()
    	fv.pointerMethod()
    	fv.valueMethod()
    }
    //output: pointer pointer value value
    

    首先我们给field定义了两个方法,一个是指针接收者的pointerMethod,一个是值接收者valueMethod

    然后我们创建了变量,fp是指针类型,fv是值类型。

    fpfv分别调用pointerMethodvalueMethod,可以看到他们都可以通过编译正常输出。

    当类型和方法的接收者类型不同时,编译器会做一些操作:

    值类型调用指针接收者方法时,实际为(&fv).pointerMethod()

    指针类型调用值接收者方法时,实际为(*fp).valueMethod()

    这里是类型不同可以相互调用的情况,再说下不能调用的情况。

    类型不同不可以调用

    不能调用的情况有两种:

    • 值类型不能被寻址
    • 指针接收者实现接口

    两种情况都是值类型不能调用指针接收者方法

    值类型不能被寻址

    如果值类型实体不能被寻址,那么它就不能调用指针接收者方法

    package main
    
    import (
    	"fmt"
    )
    
    type field struct {
    	name string
    }
    
    func (p *field) pointerMethod() {
    	fmt.Println(p.name)
    }
    
    func (p field) valueMethod() {
    	fmt.Println(p.name)
    }
    
    func NewFiled() field {
    	return field{name: "right value struct"}
    }
    
    func main() {
    	NewFiled().valueMethod()
    	NewFiled().pointerMethod()
    }
    

    运行代码报错:

    ./x.go:37:12: cannot call pointer method on NewFiled()
    ./x.go:37:12: cannot take the address of NewFiled()
    

    看来编译器首先试着给 NewFoo() 返回的右值调用 pointer method,出错;然后试图给其插入取地址符,未果,就只能报错了。

    至于左值和右值的区别,大家感兴趣可以自行搜索一下。大致来说,最重要区别就是是否可以被寻址,可以被寻址的是左值,既可以出现在赋值号左边也可以出现在右边;不可以被寻址的即为右值,比如函数返回值、字面值、常量值等等,只能出现在赋值号右边。

    指针接收者实现接口

    使用指针接收者实现接口方法,那么只有指针类型的实体实现了接口

    package main
    
    import "fmt"
    
    type human interface {
    	speak()
    	sing()
    }
    
    type man struct {
    }
    
    func (m man) speak() {
    	fmt.Println("speaking")
    }
    
    func (m *man) sing() {
    	fmt.Println("singing")
    }
    
    func main() {
    	var h human = &man{}
    	h.speak()
    	h.sing()
    }
    

    上面代码可以正常编译输出,但是我们把&man{}修改为man{}就编译不过了

    func main() {
    	var h human = man{}
    	h.speak()
    	h.sing()
    }
    

    报错如下:

    ./x.go:22:6: cannot use man literal (type man) as type human in assignment:
            man does not implement human (sing method has pointer receiver)

    man没有实现human,因为sing是个指针接收者方法。

    这里看下飞雪无情的总结:

    实体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口

    如果是值接收者,实体类型的值和指针都可以实现对应的接口;如果是指针接收者,那么只有类型的指针能够实现对应的接口

    再看下饶大的解释:

    接收者是指针类型的方法,很可能在方法中会对接收者的属性进行更改操作,从而影响接收者;而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响。

    所以,当实现了一个接收者是值类型的方法,就可以自动生成一个接收者是对应指针类型的方法,因为两者都不会影响接收者。但是,当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者。

    平时我们写代码的时候也不用可以记这个,不仅编译器会报错,goland也一样会提示。

    参考

    https://www.qtmuniao.com/2020/01/06/go-value-pointer-method/

    https://qcrao91.gitbook.io/go/interface/zhi-jie-shou-zhe-he-zhi-zhen-jie-shou-zhe-de-qu-bie

    https://www.flysnow.org/2017/04/03/go-in-action-go-interface.html

  • 相关阅读:
    抽象类与接口
    二叉树的镜像
    树的子结构
    合并两个排序的链表
    反转链表
    链表中倒数第k个结点
    调整数组顺序使奇数位于偶数前面
    230. Kth Smallest Element in a BST
    98. Validate Binary Search Tree
    94. Binary Tree Inorder Traversal(二叉树中序遍历)
  • 原文地址:https://www.cnblogs.com/keystone/p/14015867.html
Copyright © 2011-2022 走看看