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

bsxbgnwa  于 12个月前  发布在  Go
关注(0)|答案(6)|浏览(85)

我知道Go语言中的指针允许改变函数的参数,但如果它们只采用引用(带有适当的const或可变限定符),岂不是更简单。现在我们有了指针,对于一些内置类型,如map和channels,隐式的通过引用传递。
是我遗漏了什么,还是Go中的指针只是一个不必要的复杂化?

kzipqqlq

kzipqqlq1#

指针的用处有几个原因。指针允许控制内存布局(影响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的系统编程,部分原因是由于缺乏控制内存布局和使用的能力(缓存未命中会显着影响性能),性能无法调整到相同的程度。Go的目标是在许多领域取代C,因此需要支持指针。

vatpfxk5

vatpfxk52#

我很喜欢https://www.golang-book.com/books/intro/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
}
pxq42qpu

pxq42qpu3#

Go被设计成一种简洁、极简的语言。因此,它从值和指针开始。后来,根据需要,添加了一些参考类型(切片、贴图和通道)。
Go编程语言:语言设计常见问题:为什么贴图、切片和通道是引用,而数组是值?
“关于这个主题有很多历史。早期,map和channel在语法上都是指针,不可能声明或使用非指针示例。此外,我们还为数组应该如何工作而挣扎。最终我们认为指针和值的严格分离使得语言更难使用。引入引用类型,包括处理数组引用形式的切片,解决了这些问题。引用类型给语言增加了一些令人遗憾的复杂性,但它们对可用性有很大的影响:当Go语言被引入时,它变成了一种更高效、更舒适的语言。”
快速编译是Go编程语言的主要设计目标;这是有代价的其中一个损失似乎是将变量(除了基本的编译时常量)和参数标记为不可变的能力。有人提出要求,但被拒绝了。
golang-nuts : go language. Some feedback and doubts.
“将const添加到类型系统中会强制它出现在任何地方,并强制在发生更改时将其删除。虽然以某种方式将对象标记为不可变可能有一些好处,但我们不认为const类型限定符是可行的。

g0czyy6m

g0czyy6m4#

引用不能被重新分配,而指针可以。仅这一点就使得指针在许多不能使用引用的情况下很有用。

mpgws1up

mpgws1up5#

我不会在“Go”的上下文中回答这个问题,我会在任何语言的上下文中回答这个问题(例如:C、C++、Go),其实现了“指针”的概念;同样的道理也适用于“围棋”。
通常有两个存储器部分,其中进行存储器分配:堆内存和堆栈内存(让我们不包括“全局部分/内存”,因为它会脱离上下文)。

堆内存:这是大多数语言使用的:无论是Java,C#,Python..
堆栈内存:在C、C++、Go、Java等语言中,变量可以在堆栈内存中分配。堆栈内存不需要垃圾回收;因此,它是堆存储器的高性能替代方案。

但有一个问题:当我们在堆内存中分配一个对象时,我们得到一个“引用”,它可以传递给“* 多个方法/函数 ”,并且正是通过引用,“ 多个方法/函数 ”可以直接读取/更新同一个对象(在堆内存中分配)。可悲的是,堆栈内存的情况并非如此;正如我们所知,每当堆栈变量被传递给方法/函数时,它都是“按值传递”的(例如,Java),前提是你有“指针的概念”(就像C、C++、Go的情况一样)。
这里是指针进入画面的地方。指针让“
多个方法/函数 ”读取/更新堆栈内存中的数据。
简单来说,“指针”允许使用“栈内存”代替堆内存,通过“多方法/函数”处理变量/结构/对象;因此,
避免了由垃圾收集机制引起的性能冲击 *。
在Go中引入指针的另一个原因可能是:Go应该是一种“高效的系统编程语言”,就像C,C++,Rust等一样。并与底层操作系统提供的系统调用一起顺利工作,因为许多系统调用API在其原型中具有指针。
有人可能会说,这可以通过在系统调用接口之上引入一个无指针层来实现。是的,这是可以做到的,但是有指针就像非常接近系统调用层,这是一个好的系统编程语言的特点。

jyztefdp

jyztefdp6#

指针特殊变量,用于存储其他变量的内存地址,并指向其内存地址,从而提供访问该地址中存储的变量值的方法。

1.我们需要指针变量的原因之一是它们很便宜,因为它们占用固定的大小,而不管它们指向的值的大小。
1.此外,我们可以使用指针来帮助内存管理来增加变量的生存期,而不依赖于它的作用域。

相关问题