在Go中使用指针有什么意义?


98

我知道Go中的指针允许函数参数的突变,但是如果它们仅采用引用(带有适当的const或可变限定符),那不是更简单吗?现在,我们有了指针,并为一些内置类型(如地图和通道)隐式地按引用传递。

我是否缺少某些东西,或者Go中的指针仅仅是不必要的复杂化?


1
这是一个可能有助于阐明的问题:stackoverflow.com/questions/795160/…按值传递引用和按引用传递真正之间是有区别的。
R. Martinho Fernandes

1
注意:这个问题是关于Java的,但它也适用于Java。
R. Martinho Fernandes 2009年

1
“并且对于某些内置类型(例如地图和通道),隐式通过引用传递。” 不,Go中的一切都是按价值传递的。一些类型是(非正式地描述为)引用类型,因为它们具有内部可变状态。
newacct 2012年

这个问题的问题在于,“引用”并不是具有明确定义的属性的单一事物。术语“引用”非常模糊。我们可以从答案中看到有多少人将不同的事物读入“参考”一词中。因此,该问题应详细说明Go指针与该问题所考虑的引用之间的区别。
mtraceur

Answers:


36

我真的很喜欢从http://www.golang-book.com/8摘录的示例

func zero(x int) {
    x = 0
}
func main() {
    x := 5
    zero(x)
    fmt.Println(x) // x is still 5
}

与之相反

func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&x)
    fmt.Println(x) // x is 0
}

41
问题是“为什么我们要使用指针而不是引用 ”,而且我不明白为什么这个示例不能与引用一起使用。
AndreKR

@AndreKR因为我们可以选择是按引用传递还是按值传递。在某些情况下,两者都可取。
JDSweetBeat '16

9
@DJMethaneMan这是“指针与引用”,而不是“指针与传递值”!
AndreKR

作为附带说明,通过“ ref”关键字在C#2.0中添加了按引用传递。当然,在某些情况下,指针还是更方便的,因为我们可以拥有指向指针的指针...
robbie fan

33

指针之所以有用,有几个原因。指针允许控制内存布局(影响CPU缓存的效率)。在Go中,我们可以定义一个结构,其中所有成员都位于连续内存中:

type Point struct {
  x, y int
}

type LineSegment struct {
  source, destination Point
}

在这种情况下,Point结构嵌入在LineSegment结构中。但是您不能总是直接嵌入数据。如果要支持二进制树或链表之类的结构,则需要支持某种指针。

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode
}

Java,Python等不存在此问题,因为它不允许您嵌入复合类型,因此无需在语法上区分嵌入和指向。

使用Go指针解决的Swift / C#结构问题

实现此目标的一种可能替代方法是像C#和Swift一样区分structclass。但这确实有局限性。虽然通常可以指定函数将结构体作为inout参数来避免复制该结构体,但是它不允许您存储对结构体的引用(指针)。这意味着当您发现一个有用的结构(例如,创建池分配器)时,就永远不能将其视为引用类型。

自定义内存分配器

使用指针,您还可以创建自己的池分配器(这非常简化,删除了许多检查以仅显示原理):

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode

  nextFreeNode *TreeNode; // For memory allocation
}

var pool [1024]TreeNode
var firstFreeNode *TreeNode = &pool[0] 

func poolAlloc() *TreeNode {
    node := firstFreeNode
    firstFreeNode  = firstFreeNode.nextFreeNode
    return node
}

func freeNode(node *TreeNode) {
    node.nextFreeNode = firstFreeNode
    firstFreeNode = node
}

交换两个值

指针还允许您实现swap。那就是交换两个变量的值:

func swap(a *int, b *int) {
   temp := *a
   *a = *b
   *b = temp
}

结论

Java从未能够完全取代C ++在诸如Google之类的地方进行系统编程,部分原因是由于缺乏控制内存布局和使用的能力而无法将性能调整到相同的程度(高速缓存未命中会严重影响性能)。Go旨在在许多领域取代C ++,因此需要支持指针。


7
C#允许通过引用传递结构。请参见“ ref”和“ out”关键字。
olegz 2015年

1
好的,就像Swift。我将考虑一种更新示例的方法。
Erik Engheim

29

引用不能重新分配,而指针可以。仅此一项就使指针在无法使用引用的许多情况下很有用。


17
引用是否可重新分配是特定于语言的实现问题。
crantok '16

28

Go被设计为简洁的极简语言。因此,它仅从值和指针开始。后来,根据需要添加了一些参考类型(切片,地图和通道)。


Go编程语言:语言设计常见问题解答:为什么数组是值时映射,切片和通道引用?

“关于该主题有很多历史。在早期,地图和通道在语法上是指针,因此不可能声明或使用非指针实例。此外,我们在数组应如何工作方面也遇到了麻烦。最终,我们决定严格分离指针和值的使用使该语言更难使用。引入引用类型(包括用于处理数组引用形式的切片)解决了这些问题。引入这些语言后,您会获得更高效,更舒适的语言。”


快速编译是Go编程语言的主要设计目标。有其成本。伤亡之一似乎是将变量(基本编译时间常数除外)和参数标记为不可变的能力。已被要求,但是被拒绝了。


golang-nuts:语言。一些反馈和疑问。

“在类型系统中添加const会强制它出现在任何地方,如果发生某些更改会强制将其删除到任何地方。虽然以某种方式标记不可变的对象可能会有一些好处,但我们认为const类型限定符并不是去。”


FWIW,Go中的“引用类型”也可以重新分配。它们更像隐式指针吗?
马特·乔纳

1
它们只是包含指针(以及长度,容量等)的结构的特殊语法。
mk12
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.