zoukankan      html  css  js  c++  java
  • GO 的方法集

    前言

    之前在写 GOdemo 的时候, 写了这么一段程序(大概意思):

    package main
    
    type Test struct {
    }
    
    func (test *Test) print()  {
    	println("test fun")
    }
    
    func main() {
    	Test{}.print()
    }
    

    结果一编译就报错了: cannot call pointer method on Test literal

    差不多意思是不能调用指针方法. 我一看, 确实, print方法声明的是指针类型. 这么说我就懂了, 加个取址就 OK 了吧? (&Test{}).print() 这样就可以调用了.

    分析

    由此大胆的假设, GO在将方法绑定到结构体的时候, 根据接收的结构体类型不同(值或指针), 会将方法绑定到不同的类型变量上, 也就是说, 指针类型只能调用指针类型的方法, 值类型只能调用值类型的方法.

    验证一下:

    package main
    
    type Test struct {
    }
    
    func (test *Test) print()  {
    	println("test fun")
    }
    
    func (test Test) print2()  {
    	println("test fun 2")
    }
    
    func main() {
    	// 指针类型调用值类型方法
    	(&Test{}).print2()
    	// 指针类型调用指针类型方法
    	(&Test{}).print()
    	// 值类型调用值类型方法
    	Test{}.print2()
    	// 值类型调用指针类型方法
    	Test{}.print()
    }
    

    结果如何? 只有在使用值类型调用指针类型方法时, 编译会报错, 其他情况都 OK.

    假设推翻, GO方法的绑定规则应该是(网上搜了搜, 发现这玩意叫 GO 的方法集):

    1. 指针类型拥有 值/指针 的方法
    2. 值类型只拥有值类型的方法

    那么问题来了, 我平常写的时候, 是这样的, 就不会报错呀, 怎么今天突然报错了? 他们有什么区别么?

    t := Test{}
    t.print()
    

    我十分确定, t变量不是指针, 但他就可以调用呀. 查了查发现, 是GO在编译的时候帮我们隐式的做了取址的操作. 那为什么这里可以帮忙, 上面就不行了呢? 搞不懂.

    在查的时候, 还看到了大概这样的代码:

    package main
    
    // 定义个测试接口
    type ITest interface {
    	print()
    }
    
    type Test struct {
    }
    
    // 实现接口的类
    func (test *Test) print()  {
    	println("test fun")
    }
    
    func main() {
    	ReceiveTest(Test{})
    }
    
    // 接收接口的方法
    func ReceiveTest(t ITest)  {
    	t.print()
    }
    

    这个时候, 向方法传值就会报错, 有了上面的经验, 我已经知道了, 值类型没有绑定print方法, 所以改成传递指针就可以了.而且, 在这里, 如果在 ReceiveTest方法中做取址的操作, 也么的用, 只能在向方法传参的时候做取值操作.

    这里再假设一下, 方法在传参的时候是传递的复制值, 当对值进行复制传进函数的时候, 俨然已经不是原始的值了, 而是原始值的一个副本, 而对副本再进行取址, 已经是一个新地址了, 自然就没有绑定其指针函数. 而当参数是指针类型的时候, 对指针类型复制并传递, 方法接收到的是一个地址值, 虽然此地址值是一个副本, 但是指向的仍然是原对象.

    OK, 验证假设(为了保证编译顺利, 只保留了基本内容):

    package main
    
    import "fmt"
    
    type Test struct {
    	Name int
    }
    
    func main() {
    	t := Test{}
    	fmt.Printf("%p
    ", &t)
    	ReceiveTest(t)
    }
    
    func ReceiveTest(t Test)  {
    	fmt.Printf("%p
    ", &t)
    }
    

    打印结果不同, 果然不是同一个对象, 而是复制的一个副本. 而对于指针传递:

    package main
    
    import "fmt"
    
    type Test struct {
    	Name int
    }
    
    func main() {
    	t := &Test{}
    	fmt.Printf("原始指针变量的地址: %p
    ", &t)
    	fmt.Printf("原始指针变量的值: %p
    ", t)
    	ReceiveTest(t)
    }
    
    // 接收接口的方法
    func ReceiveTest(t *Test)  {
    	fmt.Printf("接收指针变量的地址: %p
    ", &t)
    	fmt.Printf("接收指针变量的值: %p
    ", t)
    }
    

    打印结果:

    原始指针变量的地址: 0xc00000e028
    原始指针变量的值: 0xc000016068
    接收指针变量的地址: 0xc00000e038
    接收指针变量的值: 0xc000016068
    

    结果发现, 指针传递保存的对象地址确实会原封不动的传递, 但是, 其指针变量却会创建副本传进来. 所以可以这样理解, 不管你是指针类型还是值类型, GO 在函数传参的时候, 都会对该内容创建一个副本进行传递.

    那也就意味着, 如果传的是一个较大的对象, 进行值的传递, 会将整个对象全拷贝一份, 然后传递过去, 而传递指针只需要拷贝8字节的指针数据就可以了,

    不过如果传入了指针类型, 就要直面在方法内部可能会对对象进行修改的风险.


    至此, 最开始的疑问已经解答了, 被GO这个t.print(), 调用方法时的隐式转址蒙蔽了我的双眼... 虽然这样在使用的时候就不用特意区分变量类型是值还是地址, 但是有的地方帮我转了, 有的地方又不管我了, 感觉怪怪的. 再习惯习惯.

  • 相关阅读:
    http状态码
    闭包
    节流和防抖
    继承方式
    array和object对比
    排序算法
    算法题
    汇编 asm 笔记
    FFMPEG 内部 YUV444P016 -> P010
    FFMPEG 内部 YUV444p16LE-> P016LE
  • 原文地址:https://www.cnblogs.com/hujingnb/p/13311200.html
Copyright © 2011-2022 走看看