在Go语言中理解指针

3

我想了解指针在Go中的工作原理。总体上,我对指针的经验很少,因为我主要使用javascript。

我写了这个虚拟程序:

func swap(a, b *int) {
    fmt.Println("3", &a, &b)
    *a, *b = *b, *a

    fmt.Println("4", a, b)
}
func main() {
    x := 15
    y := 2

    fmt.Println("1", x, y)
    fmt.Println("2", &x, &y)
    swap(&x, &y)
    fmt.Println("5", x, y)
}

以下是输出的结果:
$ go run test.go
1 15 2
2 0x208178170 0x208178178
3 0x2081ac020 0x2081ac028
4 0x208178178 0x208178170
5 2 15

我有几个问题:
  1. 根据我的理解,&x 给出 x 存储的地址。要获取 x 的实际值,需要使用 *x。那么,我不明白为什么 &x*int 类型。由于 *x&x 都是 *int 类型,它们之间的区别对我来说并不清楚。

  2. 在我的 swap 函数中,使用 *a, *b = *b, *a 和使用 a, b = b, a 有什么区别?两者都可以工作,但我无法解释原因...

  3. 为什么在第二步和第三步之间打印时地址不同?

  4. 为什么我不能直接修改地址,例如将 &b 分配给 &a

感谢您的帮助。

1
x的实际值是x。如果参数a存储了x的地址,则*a给出了x的值。 - Kerrek SB
2
我认为是*符号表示两个不同的事物,这一点让人感到困惑。我记得在多年前学习C语言时,我也有些困惑。 - Not_a_Golfer
2
3:您正在打印指针变量的地址,而不是基础整数的地址。要看到那些,请打印 ab - twotwotwo
4: 这只是他们选择定义语言的方式:他们决定变量的行为就好像它们总是住在同一个地址一样。这样做的优点是,您不必担心指针可能带来的许多问题(例如其他代码意外更改您的变量),除非您实际上选择使用指针(或使用指针实现的 Go 类型,例如切片和映射)。 - twotwotwo
3
不,a, b = b, a 在你的 swap 函数内交换了可访问的指针,但它并没有从 main 的角度交换值:输出的最后一行变成了 5 15 2。http://play.golang.org/p/rCXDgkZ9kG - twotwotwo
显示剩余2条评论
2个回答

4

1)这是一种关于语言的混淆,导致您认为*x&x是相同类型。正如Not_a_Golfer所指出的那样,在表达式和类型名称中使用*是不同的。main()中的*x是无效的语法,因为表达式中的*尝试获取指针指向的值,但x不是指针(它是一个int)。

我想您可能在考虑这样一个事实,即当您使用&x获取指向x的指针时,您添加到类型名称的字符是*,形成*int。我可以理解这很令人困惑,&var会得到*typ而不是&typ。另一方面,如果他们将&放在类型名称上,那在其他情况下就会令人困惑。有些棘手是不可避免的,就像人类语言一样,通过使用可能比仅仅讨论更容易学习。

2) 再次,结果证明假设是不准确的:a, b = b, a 交换了 swap 函数查看的指针,但从 main 的角度来看,并未交换值,输出的最后一行更改为 5 15 2http://play.golang.org/p/rCXDgkZ9kG

3) swap 打印的是指针变量的地址,而非底层整数的地址。您可以打印 ab 来查看整数的地址。

4) 我假设(也许是错误的)您希望使用 & 语法交换任意变量指向的位置,例如 &x, &y = &y, &x,而无需声明指针变量。这里存在一些歧义,如果这不是您想要的,我不确定答案的这部分是否会对您有所帮助。

与许多“为什么我不能……”问题一样,简单的答案是“因为他们定义了语言”。但是稍微深入一点,我认为你需要在某个时候将变量声明为指针(或者使用指针实现的另一种类型,比如映射或切片),因为指针会带来陷阱:例如,在一段代码中进行更改可能会以意外的方式更改其他代码的局部变量。因此,无论在哪里看到*int出现,它都告诉你可能需要担心(或者说你能够使用)像nil指针、多个代码片段的并发访问等问题。

Go在显式指针方面比其他语言更为保守:例如,C++有“引用参数”(int& i)的概念,在其中您可以进行swap(x,y)而不需要在main中出现&或指针。换句话说,在具有引用参数的语言中,您可能必须查看函数的声明才能知道它是否会更改其参数。这种行为对于Go开发人员来说有点令人惊讶/隐晦/棘手,因此他们没有采用这种方式。


无法回避的是,所有的引用和取消引用都需要一些思考,你可能需要花一些时间来处理它;尽管如此,希望这一切能有所帮助。


1
据我所知,&x 给出了 x 存储的地址。要获取 x 的实际值,需要使用 *x。然后,我不明白为什么 &x*int 类型。由于 *x&x 都是 *int 类型,它们之间的区别对我来说并不清楚。
*int 在函数声明中时,它表示 int 类型的指针。但在语句中,*x 不是 *int 类型。它表示指针的解引用。因此:
*a, *b = *b, *a

我会尽力帮助您进行翻译。以下是需要翻译的内容:

这意味着通过引用交换值。函数参数通过副本传递。因此,如果您想将值导出给调用者,您需要将变量的指针作为参数传递。

  1. 在我的交换函数中,使用*a,*b = *b,*a和使用a,b = b,a有什么区别?两者都可以工作,但我无法解释原因...

正如我所说,a,b = b,a意味着交换指针而不是交换值。

  1. 为什么在步骤2和步骤3之间打印时地址不同?

这不是一个定义好的结果。变量的地址由Go运行时处理。

  1. 为什么我不能直接修改地址,例如将&b分配给&a

并非不可能。例如:

package main

import (
    "unsafe"
)

func main() {
    arr := []int{2, 3}

    pi := &arr[0] // take address of first element of arr

    println(*pi) // print 2

    // Add 4bytes to the pointer
    pi = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(pi)) + unsafe.Sizeof(int(0))))

    println(*pi) // print 3
}

使用unsafe包,您可以更新地址值。但这并不被认可。

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