Golang初学者关于指针不一致性的问题
Golang初学者关于指针不一致性的问题
我在使用 json.Unmarshal 函数时,根据它是在 main() 函数内部调用还是在另一个函数内部调用,似乎得到了不一致的结果。以下是我的代码:
func main() {
var data = []byte(`{"lat": 132.0, "long": 111.0}`)
var loc Location
json.Unmarshal(data, &loc)
fmt.Printf("%T\n", loc) // 打印 main.Location
unmarshal(data, loc)
}
func unmarshal(data []byte, v interface{}) {
json.Unmarshal(data, v)
fmt.Printf("%T\n", v) // 打印 *main.Location
}
运行时,main 函数内的第一个 Printf 语句打印出类型为 main.Location 的值,而我的 main.unmarshal 函数内的 Printf 语句打印出类型为 *main.Location 的值。为什么会出现这种差异?这与 interface{} 参数的传递方式有关吗?
如果这个问题很基础,请见谅…… 非常感谢任何帮助。谢谢!
更多关于Golang初学者关于指针不一致性的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这真是太棒了,@calmh,而且完全说得通。非常感谢你帮助我这个新手!
更多关于Golang初学者关于指针不一致性的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
是的,但这与反序列化无关。在主函数中,你执行了
json.Unmarshal(&dl, ...)
fmt.Printf(..., dl)
也就是说,你向 Unmarshal 传递了地址(一个指针),但打印的是结构体的值。
在函数示例中,你向你的反序列化函数传递了指针,然后既对该指针进行反序列化,也打印该指针。
感谢您的快速回复——这说得通,但我还有一个补充问题。
当我将结构体值(而非指针)传递给解组函数,并将该结构体值的地址传递给 json.Unmarshal(正如 json 包的文档所要求的那样)时,Unmarshal 函数会错误地将该结构体识别为映射,如下列代码所示:
https://play.golang.org/p/__X151tp31_a
有什么想法吗?
谢谢!
是的,你不能那样做。在你最新的示例中发生的情况是,你将一个结构体值传递到了接口中(它被复制了)。你可以把接口看作一种盒子。然后,你将一个指向该接口的指针(&x)传递给 Unmarshal。它会将数据反序列化到指针所指向的任何东西里,在这个例子中是一个 interface{},由于这不是一个具体类型,所以它默认会变成一个 map。
我认为你的第一次尝试是正确的。😊
除了或许应该完全避免使用 interface{},当不需要空接口的通用性时,直接传递一个 *Location。这会让整个过程更加清晰明了。
这是因为 interface{} 参数的传递机制导致的类型差异。在 main() 中直接传递 &loc 时,编译器知道具体类型。但在 unmarshal 函数中,v interface{} 参数会进行隐式装箱,导致类型信息变化。
看这个示例就能明白:
package main
import (
"encoding/json"
"fmt"
)
type Location struct {
Lat float64 `json:"lat"`
Long float64 `json:"long"`
}
func main() {
var data = []byte(`{"lat": 132.0, "long": 111.0}`)
var loc Location
// 直接传递指针
json.Unmarshal(data, &loc)
fmt.Printf("main: %T\n", loc) // main.Location
fmt.Printf("main: %T\n", &loc) // *main.Location
// 通过函数传递
unmarshal(data, &loc)
// 验证两种方式的结果是否相同
var loc2 Location
unmarshal(data, &loc2)
fmt.Printf("loc == loc2: %v\n", loc == loc2) // true
}
func unmarshal(data []byte, v interface{}) {
// v 已经是 interface{} 类型,包含类型信息和值
fmt.Printf("unmarshal param: %T\n", v) // *main.Location
// 这里 v 本身已经是指针,不需要再取地址
json.Unmarshal(data, v)
// 使用类型断言查看具体类型
if ptr, ok := v.(*Location); ok {
fmt.Printf("type assertion: %T\n", ptr) // *main.Location
fmt.Printf("dereferenced: %T\n", *ptr) // main.Location
}
}
关键点:
- 在
main()中调用unmarshal(data, &loc)时,&loc的类型是*Location - 当
*Location赋值给interface{}参数v时,Go 会创建一个接口值,其中包含类型描述符*Location和指向loc的指针值 fmt.Printf("%T\n", v)显示的是接口值中存储的类型信息,即*main.Locationjson.Unmarshal需要的是指针,而v接口值中已经包含了指针,所以直接传递v即可
如果你在 unmarshal 函数中想看到非指针类型,需要解引用:
func unmarshal(data []byte, v interface{}) {
json.Unmarshal(data, v)
// 使用反射获取底层值
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
fmt.Printf("underlying type: %v\n", rv.Elem().Type()) // main.Location
}
}
这种差异不是 bug,而是 Go 接口类型系统的设计特性。interface{} 参数会保留传递给它的值的完整类型信息。

