论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方法,有两种修改的策略:
- 将call方法改为通过值绑定的方法,这样通过结构体对象就可以正常获取到call方法了
func (u User) Call() {
fmt.Println("user is called...")
fmt.Printf("%v\n", u)
}
- 在反射的过程中使用指针的方式去访问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个字节的地址),因此创建的副本指针也会指向该地址,因而可以修改原对象
• 对于值类型,由于值类型的变量内容就是原对象内容(即拷贝整个对象),因此创建的副本只是内容上和原对象相同,进而无法通过创建的副本对象去修改原对象