结构体指针方法中的指针可以重新分配给另一个实例吗?

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

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所表示的类型称为接收者的基本类型;它不能是指针或接口类型,并且必须在与方法相同的包中声明

所以你有两个选择:

  1. 要么编写一个简单的函数(而不是方法),它接受一个**AvlTree,你可以传递你的树指针的地址,这样函数就可以修改树指针(指向的对象)。

  2. 要么从你的函数/方法中返回树指针,并让调用者将其赋值给变量作为树指针。

关于你对返回树指针的担忧:没有任何问题。看看内置函数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 ints 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 = &amp;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:

  1. 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)

  2. 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 &quot;&lt;nil&gt;&quot;
	}
	return fmt.Sprintf(&quot;%q\n%s%v,%v\n%s&quot;, tree.value, strings.Repeat(&quot;\t&quot;, n),
        tree.left.str(n+1), tree.right.str(n+1), strings.Repeat(&quot;\t&quot;, n-1))
}

And this is how a tree is constructed and transformed:

tree := &amp;AvlTree{
	value: &quot;t&quot;,
	left: &amp;AvlTree{
		value: &quot;L&quot;,
		left: &amp;AvlTree{
			value: &quot;LL&quot;,
		},
		right: &amp;AvlTree{
			value: &quot;LR&quot;,
		},
	},
	right: &amp;AvlTree{
		value: &quot;R&quot;,
	},
}
fmt.Println(tree)
rotateLeftToRoot(&amp;tree)
fmt.Println(tree)

The original tree (without transformation):

&quot;t&quot;
	&quot;L&quot;
		&quot;LL&quot;
			&lt;nil&gt;,&lt;nil&gt;
		,&quot;LR&quot;
			&lt;nil&gt;,&lt;nil&gt;
		
	,&quot;R&quot;
		&lt;nil&gt;,&lt;nil&gt;

And the transformed tree (exactly what you wanted):

&quot;L&quot;
	&quot;LL&quot;
		&lt;nil&gt;,&lt;nil&gt;
	,&quot;t&quot;
		&quot;LR&quot;
			&lt;nil&gt;,&lt;nil&gt;
		,&quot;R&quot;
			&lt;nil&gt;,&lt;nil&gt;

huangapple
  • 本文由 发表于 2016年2月16日 08:13:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/35421495.html
匿名

发表评论

匿名网友

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

确定