为什么我对数组中嵌套结构的更改会在进行更改的函数之外丢失?

时间:2017-02-05 23:09:58

标签: go methods struct nested

我仍然在和#34;语言摔跤。我的Go进展的阶段,所以原谅我几乎肯定会遗漏一些非常明显的东西。

我定义了两个结构,一个包含另一个结构。我创建了一个外部结构的数组,将它传递给一个函数,该函数在每个内部结构上调用一个方法,修改它们的内容。此更改在函数中可见,但是当将外部结构添加到数组以进行返回时,外部函数无法查看更改。我已经尝试过无处不在的投掷指针 - 有点谢天谢地,因为它看起来很糟糕。

package main

import "github.com/davecgh/go-spew/spew"

type inner struct {
  ints []int
}

func (i *inner) grow() {
  i.ints = append(i.ints, 0)
}

type outer struct {
  inner inner
}

func growOs(os []outer) (out []outer) {
  for _, o := range os {
    o.inner.grow()
    out = append(out, o)
  }
  spew.Dump("----------during")
  spew.Dump(out)
  return
}

func main() {
  os := []outer{
    outer{},
    outer{},
  }
  spew.Dump("----------pre")
  spew.Dump(os)
  growOs(os)
  spew.Dump("----------post")
  spew.Dump(os)
}

3 个答案:

答案 0 :(得分:1)

循环中的o是副本,因此您必须:

  1. 将切片更改为[]*outer
  2. 只需使用指向每个项目的指针并修改项目。
  3. 例如:

    for i := range os {
        o := &os[i]
        o.inner.grow()
        // or just os[i].inner.grow()
    }
    return os
    
    1. 只需指定使用返回的切片:os = growOs(os)

答案 1 :(得分:1)

你应该改变

func main() {
    os := []outer{
        outer{},
        outer{},
    }
    spew.Dump("----------pre")
    spew.Dump(os)
    growOs(os)
    spew.Dump("----------post")
    spew.Dump(os)
}

func main() {
    os := []outer{
        outer{},
        outer{},
    }
    spew.Dump("----------pre")
    spew.Dump(os)
    os = growOs(os)
    spew.Dump("----------post")
    spew.Dump(os)
}

通知: os = growOs(os)

在功能

func growOs(os []outer) (out []outer) {
  for _, o := range os {
    o.inner.grow()
    out = append(out, o)
  }
  spew.Dump("----------during")
  spew.Dump(out)
  return
}

您只需返回一个新切片out,您就不会修改输入切片os

因此,在main函数中,os未被更改。

答案 2 :(得分:1)

Pointer argument vs value argument

如果函数需要指针参数,它将获得指向对象的指针,从而对对象执行的每个更改都将对对象本身产生影响。如果函数需要一个值参数,它将获得一个对象的副本,因此每个操作都将影响获得的副本而不是该对象。

func recPointer(s *string) {
    fmt.Printf("%p\n", s) // The same object
}

func recValue(s string) {
    fmt.Printf("%p\n", &s) // A new object
}

func main() {
    s := "asdf"
    fmt.Printf("%p\n", &s)
    recPointer(&s)
    recValue(s)
}

Named return values

  

Go的返回值可能会被命名。如果是这样,它们将被视为函数顶部定义的变量。

因此,很明显,命名返回值是在函数中定义的,而不是在它上面的名称空间中定义的。因此,当您返回一个命名值时,您可以使用一个新对象(再次)。

func recValue(s []string) (out []string) {
    fmt.Printf("%p\n", &s) // A new object
    out = append(out, s...)
    // Again new object. It is not the `s` from `main`
    // and obviously not the `s` var from incoming arguments.
    fmt.Printf("%p\n", &out)
    return
}

func main() {
    s := []string{"asdf"}
    fmt.Printf("%p\n", &s)
    recValue(s)
}

Fixed example

小心!我将spew.Dump替换为fmt.Printf进行演示调试。

// No named return values
// Pointer argument receiver
func growOs(os *[]outer) *[]outer {
    for _, o := range *os {
        o.inner.grow()
        *os = append(*os, o)
    }
    fmt.Printf("%p: %+v \n", os, os)
    return os
}

func main() {
    // `os` is a pointer to the object
    os := &[]outer{
        outer{},
        outer{},
    }
    fmt.Printf("%p: %+v \n", os, os)
    growOs(os)
    fmt.Printf("%p: %+v \n", os, os)
}