Go指针、引用、解引用规则:

7
我是GoLang的新手,来自Delphi、C++等领域。我非常兴奋地看到这种语言,并认为它注定成为“下一个大事件”。
我试图理解Go解析器和编译器如何处理指针和引用——似乎找不到任何一处清晰规则的地方。
例如,在下面的代码示例中,返回类型“*list.List”和本地变量“l”是指针类型,并且需要在它们的声明中使用指针符号“*”,但在使用时不必进行取消引用:“l.PushBack(i)”。但是在这段代码中,输入参数“value *int64”被声明为指针,并且必须进行取消引用才能正确使用:“var i int64 = *value / 2”。
我想这是因为“list.List”是引用类型,因此在使用时取消引用是隐含的,而“int64”是值类型,必须像C++中对值类型的任何指针一样处理:必须进行取消引用。
令我困惑的是,即使 “*list.List”必须使用“*”声明为指针类型,但在使用列表实例时,不需要进行取消引用。最初我感到很困惑。这只是“现状”,还是我错过了什么?
func GetFactors(value *int64) *list.List {

    l := list.New()

    l.PushBack(*value)

    var i int64 = *value / 2

    for ; i > 1; i-- {

        if *value%i == 0 {

            l.PushBack(i)

        }
    }

    return l

}

3
不,Go语言中没有Java的愚蠢和类型歧视。相反,在调用“成员”函数时会自动解引用指针。(这些函数实际上是在行外定义的。) - Kerrek SB
1个回答

15

所有针对 List 的方法都有 *List 接收器:(http://golang.org/pkg/container/list/)

func (l *List) Back() *Element
func (l *List) Front() *Element
func (l *List) Init() *List
...
func (l *List) Remove(e *Element) interface{}

在您的示例中,l 的类型为 *List,因此无需对它们进行取消引用。

相反,假设您有类似于以下内容:

type A struct{}
func (a  A) X() {
    fmt.Println("X")
}
func (a *A) Y() {
    fmt.Println("Y")
}

你被允许写:

a := A{}
a.X()
a.Y() // == (&a).Y()

或者你可以按照以下步骤操作:

a := &A{}
a.X() // same like == (*a).X()
a.Y()

但它只适用于方法接收者。Go 不会自动转换函数参数。假设有以下这些函数:

func A(x *int) {
    fmt.Println(*x)
}
func B(y int) {
    fmt.Println(y)
}

这是无效的:

A(5)

你必须这样做:

var x int 
A(&x)

这也是无效的:

var y *int
B(y)

你必须这样做:

B(*y)
与C#或Java不同,Go在处理结构体时不区分引用类型和值类型。一个*List是一个指针,而List则不是。修改List的字段只会修改本地副本,而修改*List的字段会修改所有“副本”(因为它们不是副本……它们都指向内存中的同一物)。
有些类型似乎隐藏了底层指针(比如切片包含一个指向数组的指针),但Go始终是按值传递的。

1
好的 - 这终于让我有些明白了。我发现使用Go有一个很大的学习曲线 - 它看起来像C/C++,但它的行为并不像它... - Vector
我还有一些困惑-请参阅Go中的移动语义:什么时候应该返回引用:*list.List,而什么时候只是list.List就足够了。我知道如果我想要“原地”修改一个值,那么我需要一个引用,但例如在我的示例代码中,仅返回值为list.List,如果不是C++11中的新“移动语义”,那么像在我的示例中返回引用而不是C++中的复制,我除了得到一个引用之外还能得到什么? - Vector
1
这类似于C#中的值类型和引用类型(http://msdn.microsoft.com/en-us/library/ms229017(v=vs.110).aspx)。在这种情况下,你肯定要使用`*List`。虽然`List`可能会起作用,但它只会复制第一个元素。其余的链表将是共享的,如果您修改它可能会导致一些奇怪的行为。 - Caleb
学习Go语言之后再学习C/C++的最佳方法可能是不要对指针系统做任何假设。有关某些解引用的语言规范在此处,它定义了隐式转换发生的时间。例如:“与选择器一样,使用指针引用具有值接收器的非接口方法将自动取消引用该指针:pt.Mv等同于(*pt)。Mv。” - init_js
为什么Go会隐式地将值转换为指针,以适应需要指针的方法接收器?为什么编译器不直接报错类型不匹配呢?这种“有选择性的隐式转换”对于我作为初学者来说很困惑。 - Tommy
在语言规范中有所描述:https://go.dev/ref/spec#Method_values与选择器一样,使用指针引用值接收器的非接口方法将自动取消引用该指针:pt.Mv 等同于 (*pt).Mv。与方法调用一样,使用可寻址值引用指针接收器的非接口方法将自动获取该值的地址:t.Mp 等同于 (&t).Mp。 - stimur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接