Pointers 围棋中指针的常见错误

Pointers 围棋中指针的常见错误,pointers,go,Pointers,Go,我已经很长时间没有使用指针了,当我使用指针的时候只是在学术环境中,多亏了C#/Java/scala,我现在有点生疏了 在Golang,人们在使用指针时会犯哪些常见错误 如果您正确使用了它们,是否有测试方法?我想,在出现问题之前,总是很难检测到内存泄漏。鉴于go是垃圾收集的,不允许使用指针算术,所以您不会有太多错误。您可以使用unsafe包来实现这一点,但它的名字本身就说明了这一点——它是不安全的 nil指针仍然存在。取消对它们的引用会引起恐慌,这有点像C#/Java中的异常——您会得到一个清晰的

我已经很长时间没有使用指针了,当我使用指针的时候只是在学术环境中,多亏了C#/Java/scala,我现在有点生疏了

在Golang,人们在使用指针时会犯哪些常见错误


如果您正确使用了它们,是否有测试方法?我想,在出现问题之前,总是很难检测到内存泄漏。

鉴于go是垃圾收集的,不允许使用指针算术,所以您不会有太多错误。您可以使用
unsafe
包来实现这一点,但它的名字本身就说明了这一点——它是不安全的

nil
指针仍然存在。取消对它们的引用会引起恐慌,这有点像C#/Java中的异常——您会得到一个清晰的错误描述和发生错误的堆栈跟踪

内存泄漏-GC几乎可以为您做任何事情,就像在C#/Java中一样。但据我所知,有一种特殊的情况——切片。删除元素通常通过创建另一个切片来完成,如下所示:

a = append(a[:i], a[i+1:]...)
此代码可能会泄漏您删除的元素。这是因为内部slice是一个包含数组(只是一个指针)、长度和容量的结构。删除元素时,新的切片可能包含相同的数组,它仍将引用您删除的元素。GC不会释放它。要解决这个问题,您需要在删除元素之前将其置零


还有指针和值方法的混淆。这不是一个错误,更像是一个你必须做出并理解的设计决策。具有值receiver的方法将获取接收器的副本,它无法修改状态。因此,如果您想修改状态,则需要指针接收器。此外,如果您的结构很大,并且您不希望每次调用methid时都复制它们,那么您也可能希望使用指针接收器。

鉴于Go是垃圾收集的,不允许使用指针算术,因此您不会犯太多错误。您可以使用
unsafe
包来实现这一点,但它的名字本身就说明了这一点——它是不安全的

nil
指针仍然存在。取消对它们的引用会引起恐慌,这有点像C#/Java中的异常——您会得到一个清晰的错误描述和发生错误的堆栈跟踪

内存泄漏-GC几乎可以为您做任何事情,就像在C#/Java中一样。但据我所知,有一种特殊的情况——切片。删除元素通常通过创建另一个切片来完成,如下所示:

a = append(a[:i], a[i+1:]...)
此代码可能会泄漏您删除的元素。这是因为内部slice是一个包含数组(只是一个指针)、长度和容量的结构。删除元素时,新的切片可能包含相同的数组,它仍将引用您删除的元素。GC不会释放它。要解决这个问题,您需要在删除元素之前将其置零


还有指针和值方法的混淆。这不是一个错误,更像是一个你必须做出并理解的设计决策。具有值receiver的方法将获取接收器的副本,它无法修改状态。因此,如果您想修改状态,则需要指针接收器。另外,如果您的结构很大,并且您不希望每次调用methid时都复制它们,那么您可能还希望使用指针接收器。

我发现的一个常见错误是,人们忘记了复杂结构中的指针

每个片、映射、接口都是一个指针。当一个结构包含另一个包含指针的结构时,您可能只会看到
S1{s2:s2}
,您认为这样复制一个结构很好:
a=b
,但实际上并不好,因为在s2内部,一个poiner变量,比如说
p
,将复制它们的地址,而不是它所指向的值。当您修改在*a.s2.p中找到的值时,*b.s2.p将返回相同的值

package main

import (
    "fmt"
)

type S1 struct {
    v int
    s2 S2
}

type S2 struct {
    p *int
}

func main() {
    x := 1
    b := S1{v: 10, s2: S2{p: &x}}
    a := b
    fmt.Printf("a = %+v\nb = %+v\n*a.s2.p = %d\n*b.s2.p = %d\na.s2.p = %p\nb.s2.p = %p\n", a, b, *a.s2.p, *b.s2.p, a.s2.p, b.s2.p)
    *a.s2.p = 5
    fmt.Printf("*a.s2.p = %d\n*b.s2.p = %d\na.s2.p = %p\nb.s2.p = %p\n", *a.s2.p, *b.s2.p, a.s2.p, b.s2.p)
}

这是一个非常简单的示例,看起来很明显存在问题,但在更大的应用程序上,这可能不是很明显


这个问题也出现在频道上。如果您发送一个指针,您将在另一端得到同一个指针(指针值的副本,即地址)。在这种情况下,与第一种情况一样,安全的解决方案是使用克隆函数来创建克隆对象。您通过频道发送克隆,并且不再在发送方使用它。

我发现的常见错误是人们忘记了复杂结构中的指针

每个片、映射、接口都是一个指针。当一个结构包含另一个包含指针的结构时,您可能只会看到
S1{s2:s2}
,您认为这样复制一个结构很好:
a=b
,但实际上并不好,因为在s2内部,一个poiner变量,比如说
p
,将复制它们的地址,而不是它所指向的值。当您修改在*a.s2.p中找到的值时,*b.s2.p将返回相同的值

package main

import (
    "fmt"
)

type S1 struct {
    v int
    s2 S2
}

type S2 struct {
    p *int
}

func main() {
    x := 1
    b := S1{v: 10, s2: S2{p: &x}}
    a := b
    fmt.Printf("a = %+v\nb = %+v\n*a.s2.p = %d\n*b.s2.p = %d\na.s2.p = %p\nb.s2.p = %p\n", a, b, *a.s2.p, *b.s2.p, a.s2.p, b.s2.p)
    *a.s2.p = 5
    fmt.Printf("*a.s2.p = %d\n*b.s2.p = %d\na.s2.p = %p\nb.s2.p = %p\n", *a.s2.p, *b.s2.p, a.s2.p, b.s2.p)
}

这是一个非常简单的示例,看起来很明显存在问题,但在更大的应用程序上,这可能不是很明显

这个问题也出现在频道上。如果您发送一个指针,您将在另一端得到同一个指针(指针值的副本,即地址)。在这种情况下,与第一种情况一样,安全的解决方案是使用克隆函数来创建克隆对象。您通过频道发送克隆,并且不再在发送方使用它