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

5 回复

这真是太棒了,@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
	}
}

关键点:

  1. main() 中调用 unmarshal(data, &loc) 时,&loc 的类型是 *Location
  2. *Location 赋值给 interface{} 参数 v 时,Go 会创建一个接口值,其中包含类型描述符 *Location 和指向 loc 的指针值
  3. fmt.Printf("%T\n", v) 显示的是接口值中存储的类型信息,即 *main.Location
  4. json.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{} 参数会保留传递给它的值的完整类型信息。

回到顶部