论go的方法集之指针与值

文章讨论了Go语言中结构体指针和值在方法集上的区别,指针类型可以访问值和指针绑定的方法,而值类型只能访问值绑定的方法。通过反射调用方法时,必须确保正确处理这一差异。Go编译器在函数调用时会对参数做副本,对于指针类型,副本仍指向原对象,允许修改;对于值类型,副本是对象的复制,不能修改原对象。
摘要由CSDN通过智能技术生成

论go的方法集之指针与值

最开始想探讨这样一个话题是因为学习go的反射时写过这样一段代码,想通过反射调用结构体中的字段和方法

type User struct {
    Id   int
    Name string
    Age  int
}

func (u *User) Call() {
    fmt.Println("user is called...")
    fmt.Printf("%v\n", u)
}

func main(){
    user := User{1, "lester", 22}
    DofiledAndMethod(user)
}

func DofiledAndMethod(input interface{}) {
    // 获取input的type
    inputType := reflect.TypeOf(input)
    fmt.Println("inputType is :", inputType.Name())
    // 获取input的value
    inputValue := reflect.ValueOf(input)
    fmt.Println("inputValue is :", inputValue)
    // 分别通过type获取里面的字段
    // 1. 获取interface的reflect.Type,通过type得到NumField,进行遍历
    // 2. 得到每个field数据类型
    // 3. 通过field有一个Interface方法拿到对应的value
    for i := 0; i < inputType.NumField(); i++ {
        field := inputType.Field(i)
        value := inputValue.Field(i).Interface()
        fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
    }

    // 通过type获取里面的方法
    for i := 0; i < inputType.NumMethod(); i++ {
        m := inputType.Method(i)
        fmt.Printf("%s: %v\n", m.Name, m.Type)
    }
}

运行的结果如下:

inputType is : User
inputValue is : {1 lester 22}
Id: int = 1                  
Name: string = lester        
Age: int = 22 

运行后才发现无法获取到结构体中的方法,当时很奇怪,为啥这获取不到call方法呢
后来在网上调研后才得出如下结论:

结构体指针和结构体对象所拥有的方法数量是不一样的:

  • 指针类型既拥有通过值绑定的方法又有通过指针类型绑定的方法,
  • 结构体对象(值)类型只拥有通过值类型绑定的方法。

也就是说,上面这段代码通过结构体对象user获取不到通过指针绑定的方法,因此如果希望访问到call方法,有两种修改的策略:

  1. 将call方法改为通过值绑定的方法,这样通过结构体对象就可以正常获取到call方法了
func (u User) Call() {
    fmt.Println("user is called...")
    fmt.Printf("%v\n", u)
}
  1. 在反射的过程中使用指针的方式去访问call方法
type User struct {
    Id   int
    Name string
    Age  int
}

func (u *User) Call() {
    fmt.Println("user is called...")
    fmt.Printf("%v\n", u)
}

func main(){
    user := User{1, "lester", 22}
    DofiledAndMethod(&user) // 此处传入指针
}

func DofiledAndMethod(input interface{}) {
    inputTypePointer := reflect.TypeOf(input)
    inputValuePointer := reflect.ValueOf(input)

    // 获取指针的元素
    inputType := inputTypePointer.Elem()
    fmt.Println("inputType is :", inputType.Name())
    inputValue := inputValuePointer.Elem()
    fmt.Println("inputValue is :", inputValue)
    for i := 0; i < inputType.NumField(); i++ {
        field := inputType.Field(i)
        value := inputValue.Field(i).Interface()
        fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
    }

    // 通过pointer获取里面的方法
    for i := 0; i < inputTypePointer.NumMethod(); i++ {
        m := inputTypePointer.Method(i)
        fmt.Printf("%s: %v\n", m.Name, m.Type)
    }
}

但是,在我们平常的通过对象访问方法也没问题呀,具体代码如下:

type User struct {
    Id   int
    Name string
    Age  int
}

func (u *User) Call() {
    fmt.Println("user is called...")
    fmt.Printf("%v\n", u)
}

func main(){
    user := User{1, "lester", 22}
    user.Call()
}

上面的user是对象而不是指针,那为什么可以调用Call方法呢,查了查发现, 是go在编译的时候帮我们隐式的做了取址的操作

这里分享一篇网上看到的博客:GO 的方法集 – 烟草的香味 (hujingnb.com),在这篇博客中详细介绍了go中指针和对象访问方法中存在的不同以及传值和传指针的异同,让我获益匪浅,尤其是对于传值和传引用的总结,概况起来如下:
不管是指针类型还是值类型,GO 在函数传参的时候,都会对该变量内容创建一个副本进行传递:
• 对于指针类型,由于指针类型的变量内容是原对象的地址(即拷贝8个字节的地址),因此创建的副本指针也会指向该地址,因而可以修改原对象
• 对于值类型,由于值类型的变量内容就是原对象内容(即拷贝整个对象),因此创建的副本只是内容上和原对象相同,进而无法通过创建的副本对象去修改原对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值