为什么我对数组中的嵌套结构所做的更改在修改函数之外丢失了?

huangapple go评论57阅读模式
英文:

Why are my changes to a nested struct in an array being lost outside of the function making the changes?

问题

我还处于学习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)
}
英文:

I'm still in the "wrestling with the language" stage of my Go progress, so forgive me for almost definitely missing something very obvious here.

I'm defining two structs, one containing the other. I make an array of the outer structs, pass it to a function, which calls a method on each of the inner structs, modifying their contents. This change is visible within the function, but when adding the outer structs to an array for returning, the outer function can't see the changes. I've tried throwing pointers in everywhere to little avail - somewhat thankfully, because it looked awful.

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)
}

答案1

得分: 3

在你的循环中,o是一个副本,所以你可以选择:

  1. 将切片改为[]*outer
  2. 简单地使用指向每个项的指针,并直接修改项。

例如:

for i := range os {
    o := &os[i]
    o.inner.grow()
    // 或者直接使用 os[i].inner.grow()
}
return os
  1. 只需将返回的切片赋值给osos = growOs(os)
英文:

The o in your loop is a copy, so you either have to:

  1. Change your slice to be []*outer.
  2. Simply use a pointer to each item and modify the items in place.

Ex:

for i := range os {
	o := &os[i]
	o.inner.grow()
    // or just os[i].inner.grow()
}
return os
  1. just assign use the returned slice: os = growOs(os)

答案2

得分: 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 没有被修改。

英文:

You should change

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

to

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

NOTICE: os = growOs(os)

in function

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

you just return a new slice out, you don't modify the input slice os,

So in your main function, the os isn't changed.

答案3

得分: 1

指针参数 vs 值参数

如果一个函数期望指针参数,它将获得一个指向对象的指针,因此对对象所做的每个更改都会对对象本身产生影响。如果一个函数期望值参数,它将获得一个对象的副本,因此每个操作都会影响到获得的副本而不是对象本身。

func recPointer(s *string) {
    fmt.Printf("%p\n", s) // 相同的对象
}

func recValue(s string) {
    fmt.Printf("%p\n", &s) // 新的对象
}

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

命名返回值

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

因此,可以清楚地看出,命名返回值是在函数内部定义的,而不是在函数上方的命名空间中。因此,当你返回一个命名值时,你实际上是返回一个新的对象。

func recValue(s []string) (out []string) {
    fmt.Printf("%p\n", &s) // 新的对象
    out = append(out, s...)
    // 再次新的对象。它不是来自 `main` 中的 `s`,
    // 显然也不是来自传入参数的 `s` 变量。
    fmt.Printf("%p\n", &out)
    return
}

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

修正的示例

注意!为了演示调试,我用 fmt.Printf 替换了 spew.Dump

// 没有命名返回值
// 指针参数接收者
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` 是一个指向对象的指针
    os := &[]outer{
        outer{},
        outer{},
    }
    fmt.Printf("%p: %+v \n", os, os)
    growOs(os)
    fmt.Printf("%p: %+v \n", os, os)
}
英文:

Pointer argument vs value argument

If a function expects pointer argument it will obtain a pointer to an object thereby each change executed upon the object will have effect on the object itself. If a function expects a value argument it will obtain a copy of an object so each operation will affect an obtained copy not the object.

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's return values may be named. If so, they are treated as variables defined at the top of the function.

Thereby it becomes clear that named return values are defined in function not in a name space above it. So when you return a named value you do it with a new object (again).

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

Be careful! I replaced spew.Dump with fmt.Printf for demonstrative debug.

// 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)
}

huangapple
  • 本文由 发表于 2017年2月6日 07:09:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/42058388.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定