指针的指针(例如**int)有什么用途?

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

What use case does pointers to pointer (eg **int) have?

问题

这是指向指针的示例代码:

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
}

你实际上在什么情况下会使用这个呢?你可能会想出一些其他更容易实现的方法,但这不是我问的。我真正想知道的是,在实际生产中,你会在哪里使用这个?

英文:

This is pointers to pointers

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

}

When would you actually use this? You can properly come up with something where it would be easier to do something else, but that is not what I asking about. I really want to know where in production you would use this?

答案1

得分: 5

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

我自己也写了几个函数,它们接受**html.Node;它们操作一个HTML页面,该页面可能已经被解析为*html.Node的树,也可能不需要解析页面,但如果需要解析,我希望保留已解析的树,以免再次解析。这些函数在github.com/andybalholm/redwood/prune.go中。

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

英文:

Pointers to pointers make sense in function parameters sometimes; not **int probably, but a pointer to a pointer to some struct, where you want the function to be able to change what object a variable points to, not just to change the contents of the struct. For example, there are a few functions in the internals of the Go compiler that take a **Node (see cmd/compile/internal/gc/racewalk.go).

I've also written a couple of functions myself that take a **html.Node; they operate on an HTML page that may or may not have already been parsed into a tree of *html.Nodes, and they may or may not need to parse the page—but if they do, I want to keep the parsed tree around so that I don't have to parse it again. These are in github.com/andybalholm/redwood/prune.go.

They are much more common in languages that do not have multiple return values, since they can be used as a way to return an additional value that is a pointer. Many Objective-C methods take an NSError** as their last parameter so that they can optionally return an NSError*.

答案2

得分: 4

将指针传递给某个东西的目的是如果有必要修改指向的值。(我们还使用指针来避免在传递时复制大型数据结构,但这只是为了优化。)

就像这个例子中的代码一样:

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>

指向指针的指针之所以不常用,是因为修改指向的值可以通过返回该值并在调用者处进行赋值来替代。

这个变体更易于阅读和维护,因此这显然是更受欢迎和广泛使用的函数修改指针值的替代方法。

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

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

请参阅以下相关问题,其中提出了指向指针的指针(但返回指针也是可行/首选的):https://stackoverflow.com/questions/35421495/golang-can-the-pointer-in-a-struct-pointer-method-be-reassigned-to-another-inst/35426997#35426997

英文:

The goal to pass a pointer to something is if there is need to modify the pointed value. (We also use pointers to avoid copying large data structures when passing, but that is just for optimization.)

Like in this example:

func main() {
	var i int
	fmt.Println(i)
	inc(&amp;i)
	fmt.Println(i)
}

func inc(i *int) {
	*i++
}

Output is the expected (try it on the Go Playground):

0
1

If parameter of inc() would receive an int only, it could only modify the copy and not the original value, and so the caller would not observe the changed value.

Same goes with pointer to pointer to something. We use pointer to pointer to something, if we need to modify the pointed value, that is the pointed pointer. Like in this example:

func main() {
	var i *int
	fmt.Println(i)
	alloc(&amp;i, 1)
	fmt.Println(i, *i)

	setToNil(&amp;i)
	fmt.Println(i)
}

func alloc(i **int, initial int) {
	*i = new(int)
	**i = initial
}

func setToNil(i **int) {
	*i = nil
}

Output (try it on the Go Playground):

&lt;nil&gt;
0x1040a130 1
&lt;nil&gt;

The reason why pointer to pointer is not really used is because modifying a pointed value can be substituted by returning the value, and assigning it at the caller:

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

Output is the same (address might be different) (try it on the Go Playground):

&lt;nil&gt;
0x1040a130 1
&lt;nil&gt;

This variant is easier to read and maintain, so this is clearly the favored and wide-spread alternative to functions having to modify a pointer value.

In languages where functions and methods can only have 1 return value, it usually requires additional "work" if the function also wants to return other values besides the pointer, e.g. a wrapper is to be created to accommodate the multiple return values. But since Go supports multiple return values, need for pointer to pointer basically drops to zero as it can be substituted with returning the pointer that would be set to the pointed pointer; and it does not require additional work and does not make code less readable.

This is a very similar case to the builtin append() function: it appends values to a slice. And since the slice value changes (its length increases, also the pointer in it may also change if a new backing array needs to be allocated), append() returns the new slice value which you need to assign (if you want to keep the new slice).

See this related question where a pointer to pointer is proposed (but also returning a pointer is also viable / preferred): https://stackoverflow.com/questions/35421495/golang-can-the-pointer-in-a-struct-pointer-method-be-reassigned-to-another-inst/35426997#35426997

答案3

得分: 0

以同样的方式,一个指向值的指针允许你有多个对同一值的引用,以便在值发生变化时保持一致的视图。而一个指向指针的指针则允许你有多个对同一引用的引用,以便在指针指向内存中的不同位置时保持一致的视图。

我不能说我在Go语言的实践中见过它的使用,至少我现在想不起来。

英文:

In the same way a pointer to a value lets you have many references to the same value for a consistent view of the value when it changes, a pointer to a pointer lets you have many references to the same reference for a consistent view of the pointer when it changes to point to a different location in memory.

I can't say I've ever seen it used in practice in Go that I can think of.

huangapple
  • 本文由 发表于 2017年7月25日 03:38:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/45288751.html
匿名

发表评论

匿名网友

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

确定