英文:
Can the pointer in a struct pointer method be reassigned to another instance?
问题
我一直在研究Golang,并且一直在实现一些数据结构来学习这门语言的工作原理。在编写AVL树的代码时,我遇到了以下问题:
在结构体指针方法中,将主要指针赋值给其他变量似乎在函数范围之外没有任何效果。例如,tree.rotateLeftToRoot()
并不会导致tree.left
成为新的树。
**问题:**在Golang中,是否有一种方法可以在结构体指针方法中重新分配指针,或者这种做法通常是不鼓励的?在这个例子中,就是"tree = prevLeft"
这一行。
代码片段:
// t.rotateLeftToRoot()的图形表示:
// t L
// L R -> LL t
//LL LR LR R
func (tree *AvlTree) rotateLeftToRoot() {
if tree == nil {
return
}
prevLeft := tree.left
if prevLeft != nil {
tree.left = prevLeft.right //tree.left传递给根节点其右子树
prevLeft.right = tree //tree成为prevLeft的右子树
tree.updateHeight()
prevLeft.updateHeight()
tree = prevLeft //期望的行为:tree.left成为新的树
//实际的行为:函数返回时没有效果
}
}
我尝试了其他设置tree值或地址的组合,但它们都没有产生预期的效果。例如,*tree = *prevLeft
会导致无限循环。
额外说明:返回tree
并设置"tree = tree.rotateLeftToRoot()"
可以避免这个问题。这样做是可行的,但似乎混合了效果并要求对返回值进行赋值,而调用者实际上只是想能够调用一个函数来更新树。
在函数内部,可以将tree
设置为prevLeft
吗?
英文:
I've been looking into Golang and have been implementing a few data structures to learn how the language works. I've come across the following issue while writing the code for an AVL tree:
Assigning the primary pointer from a struct pointer method seems to have no effect outside the scope of the function. E.g. tree.rotateLeftToRoot()
doesn't result in tree.left
becoming the new tree.
Question: Is there a way to reassign the pointer in a struct pointer method in Golang, or is this generally discouraged? In the example this would be the "tree = prevLeft"
line.
Code snippet:
//Graphical representation of t.rotateLeftToRoot():
// t L
// L R -> LL t
//LL LR LR R
func (tree *AvlTree) rotateLeftToRoot() {
if tree == nil {
return
}
prevLeft := tree.left
if prevLeft != nil {
tree.left = prevLeft.right //tree.left passed root its right branch
prevLeft.right = tree //tree becomes tree.left's right branch
tree.updateHeight()
prevLeft.updateHeight()
tree = prevLeft //desired behaviour: tree.left becomes the new tree
//actual behaviour: no effect when function returns
}
}
I've tried other combinations of setting the value or address of tree, and none of them had the intended effect. For example, *tree = *prevLeft
results in an infinite loop.
Additional note: Returning tree
and setting "tree = tree.rotateLeftToRoot()"
avoids the issue. This works, but it seems dirty to be mixing effects and requiring assignment to returned values, when the caller really just wants to be able to call a function to update the tree.
Can the tree
be set to prevLeft
from within the function?
答案1
得分: 14
指针和int
数字一样,都是值。不同之处在于对该值的解释:指针被解释为内存地址,而int
被解释为整数。
当你想要改变类型为int
的变量的值时,你传递一个指向该int
的指针,该指针的类型是*int
,然后你修改指向的对象:*i = newvalue
(赋值的值是一个int
)。
指针也是一样的道理:当你想要改变指针类型为*int
的变量的值时,你传递一个指向该*int
的指针,该指针的类型是**int
,然后你修改指向的对象:*i = &newvalue
(赋值的值是一个*int
)。
传递指针是必需的,因为你传递的一切都会被复制,你只能修改副本。当你传递一个指针时,同样的事情也发生了:该指针也被复制了一份,但我们修改的不是指针本身,而是指向的值。
你想要修改类型为*AvlTree
的变量。在Go语言中,接收者不能是指向指针的指针。规范:方法声明:
接收者的类型必须是
T
或*T
(可能使用括号),其中T
是类型名。T
所表示的类型称为接收者的基本类型;它不能是指针或接口类型,并且必须在与方法相同的包中声明。
所以你有两个选择:
-
要么编写一个简单的函数(而不是方法),它接受一个
**AvlTree
,你可以传递你的树指针的地址,这样函数就可以修改树指针(指向的对象)。 -
要么从你的函数/方法中返回树指针,并让调用者将其赋值给变量作为树指针。
关于你对返回树指针的担忧:没有任何问题。看看内置函数append()
:它将元素追加到切片中,并返回修改后的切片。你(调用者)必须将返回的切片赋值给你的切片变量,因为append()
可能通过分配新的切片来修改切片,如果额外的元素无法适应原始切片(并且由于append()
接受的是非指针,所以必须返回修改后的值)。
以下是选择#1的解决方案示例:
func rotateLeftToRoot(ptree **AvlTree) {
tree := *ptree
if tree == nil {
return
}
prevLeft := tree.left
if prevLeft != nil {
tree.left = prevLeft.right
prevLeft.right = tree
tree = prevLeft
}
*ptree = tree
}
我在Go Playground上实现了它,以证明它的工作原理。
我使用了这个类型:
type AvlTree struct {
value string
left *AvlTree
right *AvlTree
}
为了方便检查结果,我实现了一些方法来生成string
表示:
func (tree *AvlTree) String() string { return tree.str(1) }
func (tree *AvlTree) str(n int) string {
if tree == nil {
return "<nil>"
}
return fmt.Sprintf("%q\n%s%v,%v\n%s", tree.value, strings.Repeat("\t", n),
tree.left.str(n+1), tree.right.str(n+1), strings.Repeat("\t", n-1))
}
这是如何构建和转换树的示例:
tree := &AvlTree{
value: "t",
left: &AvlTree{
value: "L",
left: &AvlTree{
value: "LL",
},
right: &AvlTree{
value: "LR",
},
},
right: &AvlTree{
value: "R",
},
}
fmt.Println(tree)
rotateLeftToRoot(&tree)
fmt.Println(tree)
原始树(未经转换):
"t"
"L"
"LL"
<nil>,<nil>
,"LR"
<nil>,<nil>
,"R"
<nil>,<nil>
转换后的树(与你想要的完全相同):
"L"
"LL"
<nil>,<nil>
,"t"
"LR"
<nil>,<nil>
,"R"
<nil>,<nil>
英文:
Pointers are values just like let's say int
numbers. The difference is the interpretation of that value: pointers are interpreted as memory addresses, and int
s are interpreted as integer numbers.
When you want to change the value of a variable of type int
, you pass a pointer to that int
which is of type *int
, and you modify the pointed object: *i = newvalue
(the value assigned is an int
).
Same goes with pointers: when you want to change the value of a variable of pointer type *int
, you pass a pointer to that *int
which is of type **int
and you modify the pointed object: *i = &newvalue
(the value assigned is an *int
).
<sup>Passing a pointer is required because a copy is made from everything you pass, and you could only modify the copy. When you pass a pointer, the same thing happens: a copy is also made of that pointer, but we're not modifying the pointer itself but the pointed value.</sup>
You want to modify a variable of type *AvlTree
. In Go the receiver cannot be a pointer to pointer. Spec: Method declarations:
> The receiver's type must be of the form T
or *T
(possibly using parentheses) where T
is a type name. The type denoted by T
is called the receiver base type; it must not be a pointer or interface type and it must be declared in the same package as the method.
So you have 2 choices:
-
either write a simple function (not method) that takes a
**AvlTree
and you can pass the address of your tree pointer, so the function can modify the tree pointer (the pointed object) -
or return the tree pointer from your function/method and have the caller assign it to the variable being the tree pointer.
Addressing your concerns regarding returning the tree pointer: there's nothing wrong with that. Take a look at the builtin function append()
: it appends elements to a slice and returns the modified slice. You (the caller) have to assign the returned slice to your slice variable, because append()
may modify the slice by allocating a new one if the additional elements do not fit into the original (and since append()
takes a non-pointer, the modified value must be returned).
Here's how the solution going with #1 would look like:
func rotateLeftToRoot(ptree **AvlTree) {
tree := *ptree
if tree == nil {
return
}
prevLeft := tree.left
if prevLeft != nil {
tree.left = prevLeft.right
prevLeft.right = tree
tree = prevLeft
}
*ptree = tree
}
I've implemented it on the Go Playground to prove it works.
I've used this type:
type AvlTree struct {
value string
left *AvlTree
right *AvlTree
}
And to easily check the result, I've implemented some methods to produce a string
representation:
func (tree *AvlTree) String() string { return tree.str(1) }
func (tree *AvlTree) str(n int) string {
if tree == nil {
return "<nil>"
}
return fmt.Sprintf("%q\n%s%v,%v\n%s", tree.value, strings.Repeat("\t", n),
tree.left.str(n+1), tree.right.str(n+1), strings.Repeat("\t", n-1))
}
And this is how a tree is constructed and transformed:
tree := &AvlTree{
value: "t",
left: &AvlTree{
value: "L",
left: &AvlTree{
value: "LL",
},
right: &AvlTree{
value: "LR",
},
},
right: &AvlTree{
value: "R",
},
}
fmt.Println(tree)
rotateLeftToRoot(&tree)
fmt.Println(tree)
The original tree (without transformation):
"t"
"L"
"LL"
<nil>,<nil>
,"LR"
<nil>,<nil>
,"R"
<nil>,<nil>
And the transformed tree (exactly what you wanted):
"L"
"LL"
<nil>,<nil>
,"t"
"LR"
<nil>,<nil>
,"R"
<nil>,<nil>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论