【问题标题】:What use case does pointers to pointer (eg **int) have?指向指针(例如**int)的指针有什么用例?
【发布时间】:2017-07-24 19:38:39
【问题描述】:

这是指向指针的指针

package main

import "fmt"

func main() {
    var num int

    fmt.Println(&num) //  0x...0
    makePointer(&num)
}

func makePointer(firstPointer *int) {
    fmt.Println(firstPointer)  //  0x...0
    fmt.Println(&firstPointer) //  0x...1

    makePointerToAPointer(&firstPointer)
}

func makePointerToAPointer(secondPointer **int) {
    fmt.Println(secondPointer)  //  0x...1
    fmt.Println(&secondPointer) //  0x...2

}

你什么时候会真正使用它?你可以适当地想出一些更容易做其他事情的事情,但这不是我要问的。我真的很想知道你会在生产中的什么地方使用它?

【问题讨论】:

    标签: pointers go


    【解决方案1】:

    指向指针的指针有时在函数参数中是有意义的;可能不是**int,而是指向某个结构的指针的指针,您希望函数能够更改变量指向的对象,而不仅仅是更改结构的内容。例如,Go 编译器内部有一些函数采用**Node(参见 cmd/compile/internal/gc/racewalk.go)。

    我自己也编写了几个函数,它们采用**html.Node;他们在一个 HTML 页面上操作,该页面可能已经或可能没有被解析成 *html.Nodes 的树,他们可能需要也可能不需要解析页面 - 但如果他们这样做,我想保留解析的树我不必再次解析它。这些位于 github.com/andybalholm/redwood/prune.go。

    它们在没有多个返回值的语言中更为常见,因为它们可以用作返回附加值(即指针)的一种方式。许多 Objective-C 方法将NSError** 作为最后一个参数,因此它们可以选择返回NSError*。

    【讨论】:

      【解决方案2】:

      传递指向某物的指针的目标是是否有需要修改指向的值。 (我们还使用指针来避免在传递时复制大型数据结构,但这只是为了优化。)

      就像这个例子:

      func main() {
          var i int
          fmt.Println(i)
          inc(&i)
          fmt.Println(i)
      }
      
      func inc(i *int) {
          *i++
      }
      

      输出是预期的(在Go Playground 上尝试):

      0
      1
      

      如果inc()的参数只接收int,它只能修改副本而不是原始值,因此调用者不会观察到更改的值。

      指向某事物的指针也是如此。我们用指针来指向某个东西,如果我们需要修改指向的值,那就是被指向的指针。就像在这个例子中一样:

      func main() {
          var i *int
          fmt.Println(i)
          alloc(&i, 1)
          fmt.Println(i, *i)
      
          setToNil(&i)
          fmt.Println(i)
      }
      
      func alloc(i **int, initial int) {
          *i = new(int)
          **i = initial
      }
      
      func setToNil(i **int) {
          *i = nil
      }
      

      输出(在Go Playground上试试):

      <nil>
      0x1040a130 1
      <nil>
      

      之所以没有真正使用指针指针,是因为修改一个指向的值可以通过返回值来代替,并在调用者处赋值:

      func main() {
          var i *int
          fmt.Println(i)
          i = alloc(1)
          fmt.Println(i, *i)
      
          i = setToNil()
          fmt.Println(i)
      }
      
      func alloc(initial int) *int {
          i := new(int)
          *i = initial
          return i
      }
      
      func setToNil() *int {
          return nil // Nothing to do here, assignment happens at the caller!
      }
      

      输出相同(地址可能不同)(在Go Playground上试试):

      <nil>
      0x1040a130 1
      <nil>
      

      此变体更易于阅读和维护,因此对于必须修改指针值的函数而言,这显然是最受欢迎且广泛使用的替代方案。

      在函数和方法只能有 1 个返回值的语言中,如果函数还想返回指针之外的其他值,通常需要额外的“工作”,例如将创建一个包装器来容纳多个返回值。但是由于 Go 支持多个返回值,指针指针的需求基本上下降到零,因为它可以用返回将被设置为指向指针的指针代替;而且它不需要额外的工作,也不会降低代码的可读性。

      这与内置的append() 函数非常相似:它将值附加到切片。并且由于切片值发生变化(它的长度增加,如果需要分配新的后备数组,其中的指针也可能发生变化),append() 返回您需要分配的新切片值(如果您想保留新切片)。

      请参阅提出指向指针的指针的相关问题(但返回指针也是可行/首选的):Golang: Can the pointer in a struct pointer method be reassigned to another instance?

      【讨论】:

      • goroutine 改变一个值怎么样。你不能在那里使用返回值。您可以使用通道将值更改输出,但这可能有点矫枉过正。当然,您可能需要使用互斥锁来访问变量。
      • @TehSphinX 我仍然认为返回指针更容易阅读和维护,即使在 goroutine 中运行它也是如此。为了方便起见,我会简单地使用一个匿名函数,您可以在其中处理返回值,例如go func() { i = alloc(1) }()
      【解决方案3】:

      与指向值的指针一样,您可以对同一值进行多次引用,以便在值更改时保持一致的视图,指向指针的指针允许您对同一引用进行多次引用以保持一致的视图当指针更改为指向内存中的不同位置时,指针的变化。

      我不能说我曾经在 Go 的实践中看到过我能想到的。

      【讨论】:

      • 我知道,只是我真的看不出一个比做其他事情更具可读性的例子。我真的想要一个具体的例子,在这个例子中使用指向指针而不是其他选项是有意义的。
      • 可能没有一个强大的用例,这可能就是为什么你从来没有看到它在野外使用的原因。你为什么要找一个理由去使用没有人用过的东西?
      • 指向指针的指针与指向接口的指针一样有用。两者都很少见,但有时它们是工作的正确工具。
      • 这是 C 代码,但原则应该转移:Linux Torvalds 的 go to pointers to pointers 示例grisha.org/blog/2013/04/02/linus-on-understanding-pointers
      • @J.Eng 最后一段代码中的键是pp = &amp;entry-&gt;next 和*pp = entry-&gt;next。 pp 将始终是“头”指针或“下一个”指针的地址。如果它是“下一个”指针,则您总是在更新前一个条目的“下一个”指针。如果您仍然感到困惑,请尝试在pp = &amp;entry-&gt;next; 行之前添加listEntry *prev = entry; 行,并将其更改为pp = &amp;prev-&gt;next; 和entry = prev-&gt;next;,看看是否有意义。
      猜你喜欢
      • 2017-06-19
      • 1970-01-01
      • 1970-01-01
      • 2015-09-10
      • 1970-01-01
      • 2018-02-09
      • 1970-01-01
      • 2013-07-11
      • 2017-09-03
      相关资源
      最近更新 更多