代码之家  ›  专栏  ›  技术社区  ›  ollien

指向接口的指针与在取消标记JSON时保持指针的接口

  •  0
  • ollien  · 技术社区  · 7 年前

    考虑以下结构和接口定义。

    type Foo interface {
        Operate()
    }
    
    type Bar struct {
        A int
    }
    
    func (b Bar) Operate() {
        //...
    }
    

    现在,如果我们尝试执行以下操作( playground ):

    var x Foo = Bar{}
    err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
    fmt.Printf("x: %+v\nerr: %s\n", x, err) 
    

    我们得到以下输出:

    x: {A:0}
    err: json: cannot unmarshal object into Go value of type main.Foo
    

    但是,通过将基础数据替换为结构类型,它不会有障碍。( playground ):

    var x Foo = &Bar{}
    err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
    fmt.Printf("x: %+v\nerr: %s\n", x, err)
    

    输出:

    x: &{A:5}    
    err: %!s(<nil>)
    

    然而,这让我很困惑。在调用unmarshall时,我们仍在传递一个指向x的指针,据我所知,它应该足以允许我们修改下面的条。毕竟,指针只是内存中的地址。如果我们要传递那个地址,我们应该能够修改它,不是吗?为什么第二个例子有效,而不是第一个?为什么结构是一个指针会产生所有的区别?

    1 回复  |  直到 7 年前
        1
  •  3
  •   icza    7 年前

    不同行为的根源在于 json 包必须创建一个新值,该值必须是具体类型,不能是(非具体)接口类型。

    让我们详细说明一下。

    首先,让我们检查一下您的第二个工作示例。你的变量 x 类型 Foo 包装类型的指针值 *Bar 。当你通过时 &x json.Unmarshal() , the 杰森 包将得到一个 *Foo 指针值。指向接口的指针!不应该使用,但它确实存在。这个 杰森 包将取消对指针的引用,并获取类型的包装值 *酒吧 . 因为这不是- nil 指针, 杰森 包裹可以“并将”继续使用,用于解组。一切都好!这个 杰森 包将修改 指出 价值。

    在第一个例子中会发生什么?

    在第一个例子中, X 类型的变量 包装非指针结构值。 无法修改包装在接口中的值。

    这是什么意思?这个 杰森 包将再次收到类型的值 *富 。然后它继续进行并取消引用,得到一个 Bar 值包装在接口中。这个 酒吧 内部值 无法修改接口。唯一的方法是 杰森 要“交付”的包,结果将是创建一个实现 ,并将此值存储在最初传递 *富 指向。但价值观 无法创建,它是非具体接口类型。所以unmarshal返回时出错。

    一些附录。您的第二个工作示例:

    var x Foo = &Bar{}
    err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
    fmt.Printf("x: %+v\nerr: %s\n", x, err)
    

    这是可行的,因为我们“准备”了一个- *酒吧 值,因此 杰森 包本身不必创建值。我们已经准备好并传递了 接口类型(为混凝土类型的值 *酒吧 )

    如果我们要存储一个“打印的” 指向 X 这样地:

    var x Foo = &Bar{}
    x = (*Bar)(nil)
    err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
    fmt.Printf("x: %+v\nerr: %s\n", x, err)
    

    我们将返回相同的错误(在 Go Playground ):

    x: <nil>
    err: json: cannot unmarshal object into Go value of type main.Foo
    

    解释是一样的 杰森 包不能使用 指向将值取消标记为的指针。它还需要创建一个非具体类型的值 但这是不可能的。只能创建具体类型的值。