2

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

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

为什么,或者什么时候,我想做bigBox := &BigBox{}而不是bigBox := BigBox{}?它在某种程度上更有效吗?

代码示例取自这里

样品 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的原因吗?

4

5 回答 5

5

我发现了这种代码的一个动机:避免“意外复制结构”。


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

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不会复制整个结构并且可以正常工作。

于 2013-09-25T10:16:29.917 回答
3

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

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

bb := &BigBox{
    BubbleGumsCount: 4,
    SmallBox: {
        AnyMagicItem: true,
    },
}
于 2013-09-22T09:34:04.063 回答
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。调整大小后的切片必须在达到其容量时创建一个更大的数组。这意味着必须将旧数组的内存复制到新数组中。

为什么你不用指针什么的?

  1. 如果一个对象很小,最好只是制作一个副本。特别是如果它 <= 8 个字节。
  2. 使用指针会产生垃圾。这些垃圾必须由垃圾收集器收集。垃圾收集器是一种标记和清除停止世界的实现。这意味着它必须冻结您的应用程序以收集垃圾。它必须收集的垃圾越多,暂停的时间就越长。以这个人为例。经历了长达 10 秒的停顿。
  3. 复制对象使用堆栈而不是堆。堆栈通常总是比堆​​快。你真的不必考虑 Go 中的堆栈与堆,因为它决定了应该去哪里,但你也不应该忽略它。这实际上取决于编译器的实现,但是指针会导致内存在堆上,从而需要进行垃圾收集。
  4. 直接内存访问更快。如果您有一个切片 []BigBox 并且它不改变大小,则访问速度会更快。[]BigBox 读取速度更快,而 []*BigBox 调整大小更快。

我的一般建议是谨慎使用指针。除非您正在处理需要传递的非常大的对象,否则通常最好在堆栈上传递一个副本。减少垃圾是一件大事。垃圾收集器会变得更好,但最好将其保持在尽可能低的水平。

一如既往地测试您的应用程序并对其进行分析

于 2013-09-26T16:59:02.953 回答
1

区别在于创建引用对象(使用 & 号)与创建值对象(不带 & 号)。

这里对值与引用类型传递的一般概念有一个很好的解释......按引用传递与按值传递有什么区别?

关于 Go here 有一些关于这些概念的讨论... http://www.goinggo.net/2013/07/understanding-pointers-and-memory.html

于 2013-09-21T22:29:01.073 回答
1

一般来说, a&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允许这样做,但我无法做到这一点。这里的教训是编译器会做它想做的任何事情。正如寄存器的细节与代码一样,堆/堆栈的状态也是如此。不要因为你认为你知道编译器将如何使用堆而改变你的代码。

于 2013-09-25T21:01:00.653 回答