【问题标题】:Can the pointer in a struct pointer method be reassigned to another instance?可以将结构指针方法中的指针重新分配给另一个实例吗?
【发布时间】:2016-05-27 01:53:52
【问题描述】:

我一直在研究 Golang,并一直在实现一些数据结构来了解该语言的工作原理。我在编写 AVL 树的代码时遇到了以下问题:

从结构指针方法分配主指针似乎在函数范围之外没有任何影响。例如。 tree.rotateLeftToRoot() 不会导致 tree.left 成为新树。

问题: 有没有办法在 Golang 的结构指针方法中重新分配指针,或者这通常是不鼓励的?在示例中,这将是 "tree = prevLeft" 行。

代码sn-p:

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

我尝试了其他设置树的值或地址的组合,但都没有达到预期的效果。例如,*tree = *prevLeft 会导致无限循环。

附加说明:返回tree 并设置"tree = tree.rotateLeftToRoot()" 可以避免该问题。这可行,但是当调用者真的只想能够调用函数来更新树时,混合效果并要求分配返回值似乎很脏。

可以在函数中将tree 设置为prevLeft 吗?

【问题讨论】:

  • 如果你想修改别人持有的tree *,你需要编写一个函数来接收(或接收)tree **。为了修改一个指针,你需要一个指向它的指针。
  • 我会让它返回更新后的*AvlTree。或者,你可以改变你的类型定义,所以你现在称之为AvlTree的东西被命名为Node(或者node,如果你不想公开它)并定义type AvlTree *Node,所以@上的一个方法987654337@可以改变节点指针。 (这将像@hobbs 所说的那样执行,只是名称不同。)使用指针到指针可能会导致更多的分配和更多的指针追逐,但可能还不够关心。跨度>
  • @hobbs 感谢您的建议。我尝试将树** 传递给函数,它似乎有完全相同的问题。有没有我错过的步骤? func rotateLeftToRoot(ptrToTreePtr **AvlTree) { ... tree := *ptrToTreePtr /*manipulations on tree, tree = prevLeft*/ *ptrToTreePtr = tree }一旦函数返回,树就指向原来的节点而不是prevLeft。
  • 我再次尝试更新测试,本地函数 rotateLeft(ptree **AvlTree) 成功运行。感谢 hobbs 和 twotwotwo 的建议。

标签: pointers methods go tree


【解决方案1】:

指针是值,就像int 数字一样。不同之处在于该值的解释:指针被解释为内存地址,ints 被解释为整数。

当你想改变一个int类型的变量的值时,你传递一个指向int的指针,它是*int类型的,然后你修改指向的对象:*i = newvalue(分配的值是int)。

指针也是如此:当你想改变一个指针类型为*int的变量的值时,你将一个指针传递给*int,它的类型是**int,然后你修改指向的对象:@987654334 @(分配的值为*int)。

需要传递一个指针,因为你传递的所有东西都会产生一个副本,你只能修改这个副本。当你传递一个指针时,同样的事情会发生:该指针也有一个副本,但我们不是修改指针本身,而是修改 pointed 值。

您想修改*AvlTree 类型的变量。在 Go 中,接收者不能是指向指针的指针。 Spec: Method declarations:

接收者的类型必须是T*T(可能使用括号)形式,其中T 是类型名称。 T 表示的类型称为接收者基本类型不能是指针或接口类型,并且必须与方法在同一个包中声明。

所以你有两个选择:

    1234563 /li>
  1. 或从您的函数/方法返回树指针,并让调用者将其分配给作为树指针的变量。

解决您对返回树指针的担忧:这没有什么问题。看一下内置函数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>

【讨论】:

  • 感谢 icza 的详尽回答。我有一个返回新指针的工作版本,并且正在寻找一种从函数或结构方法中更新指针值的方法,如您的选项 1 中所述。我再次尝试了重写测试,它按预期工作。大部分信息已经被其他人的 cmets 理解或包含,但这是一个格式非常好的帖子,可能对其他人也有用。
  • 在保留返回新值的结构方法和更新结构指针值的本地函数之间是否存在 Golang 偏好?
  • @Saigo 哪个适合你。传递**AvlTree 更安全,因为调用者不必记住分配返回值,但另一种解决方案更容易理解,它可以是一个方法(而不仅仅是一个函数)。为了安全起见,我会选择**AvlTree,但这只是我个人的喜好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-16
  • 2019-06-26
  • 2015-01-15
  • 1970-01-01
  • 2013-03-04
相关资源
最近更新 更多