这种“模式”背后的动机是什么?

时间:2013-09-21 22:03:05

标签: pointers memory go

当我看到如下代码时,我有点困惑:

bigBox := &BigBox{}
bigBox.BubbleGumsCount = 4          // correct...
bigBox.SmallBox.AnyMagicItem = true // also correct

为什么或何时,我想要bigBox := &BigBox{}代替bigBox := BigBox{}?它在某种程度上更有效吗?

代码示例取自here

样品2:

package main

import "fmt"

type Ints struct {
  x int
  y int
}

func build_struct() Ints {
  return Ints{0,0}
}

func build_pstruct() *Ints {
  return &Ints{0,0}
}

func main() {
  fmt.Println(build_struct())
  fmt.Println(build_pstruct())
}

样品编号。 3 :(为什么我会在这个例子中使用& BigBox,而不是直接使用BigBox作为结构?)

func main() {
  bigBox := &BigBox{}
  bigBox.BubbleGumsCount = 4 
  fmt.Println(bigBox.BubbleGumsCount)
}

是否有理由调用build_pstruct而不是build_struct变体?这不是我们拥有GC的原因吗?

5 个答案:

答案 0 :(得分:5)

我找到了这种代码的一个动机:避免“偶然复制结构”。


如果使用struct变量来保存新创建的结构:

bigBox := BigBox{}

你可以像这样意外复制结构

myBox := bigBox // Where you just want a refence of bigBox.
myBox.BubbleGumsCount = 4

或者像这样

changeBoxColorToRed(bigBox)

其中changeBoxColorToRed

// It makes a copy of entire struct as parameter. 
func changeBoxColorToRed(box bigBox){
    // !!!! This function is buggy. It won't work as expected !!!
    // Please see the fix at the end.
    box.Color=red
}

但是如果使用结构指针:

bigBox := &BigBox{}

中没有复制
myBox := bigBox

changeBoxColorToRed(bigBox)

将无法编译,让您有机会重新考虑changeBoxColorToRed的设计。修复很明显:

func changeBoxColorToRed(box *bigBox){
    box.Color=red
}

changeBoxColorToRed的新版本不会复制整个结构并且可以正常工作。

答案 1 :(得分:3)

bb := &BigBox{}创建一个结构,但将变量设置为指向它的指针。它与bb := new(BigBox)相同。另一方面,bb := BigBox{}使bb成为BigBox类型的变量。如果你想要一个指针(因为可能是因为你要通过指针使用数据),那么最好将bb作为指针,否则你将会编写很多&bb。如果您要将数据直接用作结构,那么您希望bb成为结构体,否则您将使用*bb取消引用。

这不是问题的关键,但通常最好一次创建数据,而不是通过创建对象并随后更新它来递增。

bb := &BigBox{
    BubbleGumsCount: 4,
    SmallBox: {
        AnyMagicItem: true,
    },
}

答案 2 :(得分:2)

&获取某个地址。所以它意味着"我想要指向"而不是"我想要一个"的实例。包含值的变量的大小取决于值的大小,可以是大小。包含指针的变量的大小为8个字节。

以下是示例及其含义:

bigBox0 := &BigBox{} // bigBox0 is a pointer to an instance of BigBox{}
bigBox1 := BigBox{} // bigBox1 contains an instance of BigBox{}
bigBox2 := bigBox // bigBox2 is a copy of bigBox
bigBox3 := &bigBox // bigBox3 is a pointer to bigBox
bigBox4 := *bigBox3 // bigBox4 is a copy of bigBox, dereferenced from bigBox3 (a pointer)

为什么要指针?

  1. 防止在将大对象作为参数传递给函数时复制大对象。
  2. 您希望通过将其作为参数传递来修改该值。
  3. 保持切片,由数组支持,小。 [10] BigBox将占据BigBox"的大小。 * 10个字节。 [10] * BigBox将占用8个字节* 10.调整大小时的切片必须在达到其容量时创建更大的数组。这意味着必须将旧数组的内存复制到新数组。
  4. 为什么不使用指针?

    1. 如果对象很小,最好只制作副本。特别是如果它< = 8字节。
    2. 使用指针可以创建垃圾。这个垃圾必须由垃圾收集器收集。垃圾收集器是一种标记和扫描停止世界的实现。这意味着它必须冻结您的应用程序以收集垃圾。它必须收集的垃圾越多,暂停的时间就越长。 This individual, for example. experienced a pause up to 10 seconds.
    3. 复制对象使用堆栈而不是堆。堆栈通常总是比堆​​快。你真的不必考虑Go中的堆栈与堆,因为它决定了应该去哪里,但你也不应该忽略它。它实际上取决于编译器实现,但指针可能导致内存进入堆,导致需要进行垃圾回收。
    4. 直接内存访问速度更快。如果你有一个切片[] BigBox并且它没有改变大小,那么它可以更快地访问。 [] BigBox读取速度更快,而[] * BigBox调整速度更快。
    5. 我的一般建议是谨慎使用指针。除非您处理需要传递的非常大的对象,否则在堆栈上传递副本通常会更好。减少垃圾是一件大事。垃圾收集器会变得更好,但是通过尽可能降低垃圾收集器会让你感觉更好。

      始终test您的申请和profile

答案 3 :(得分:1)

区别在于创建参考对象(使用&符号)与值对象(不使用&符号)。

对于传递价值与参考类型的一般概念有一个很好的解释...... What's the difference between passing by reference vs. passing by value?

关于Go here ... http://www.goinggo.net/2013/07/understanding-pointers-and-memory.html

,对这些概念进行了一些讨论

答案 4 :(得分:1)

一般情况下,&BigBox{}BigBox{}之间没有区别。只要语义正确,Go编译器就可以自由地做任何事情。

func StructToStruct() {
    s := Foo{}
    StructFunction(&s)
}

func PointerToStruct() {
    p := &Foo{}
    StructFunction(p)
}

func StructToPointer() {
    s := Foo{}
    PointerFunction(&s)
}

func PointerToPointer() {
    p := &Foo{}
    PointerFunction(p)
}

//passed as a pointer, but used as struct
func StructFunction(f *Foo) {
    fmt.Println(*f)
}

func PointerFunction(f *Foo) {
    fmt.Println(f)
}

大会摘要:

  • StructToStruct:13行,无分配
  • PointerToStruct:16行,无分配
  • StructToPointer:20行,分配堆
  • PointerToPointer:12行,堆分配

使用完美的编译器,*ToStruct函数与*ToPointer函数的函数相同。 Go的转义分析足以判断指针是否在模块边界内转义。哪种方式最有效的是编译器的方式。

如果您真的想进入微优化,请注意,当语法与语义对齐时,Go是最有效的(结构用作结构,指针用作指针)。或者你可以忘记它,并按照它的使用方式声明变量,大部分时间你都是正确的。

注意:如果Foo真的很大PointerToStruct将堆分配它。该规范威胁说,即使StructToStruct被允许这样做,但我无法实现。这里的教训是编译器会做任何想做的事情。正如寄存器的细节与代码屏蔽一样,堆/堆栈的状态也是如此。不要更改代码,因为您认为您知道编译器将如何使用堆。