理解 Go 中的指针

Understanding pointers in Go

我想了解指针在 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. 在我的交换函数中,使用*a, *b = *b, *a和使用a, b = b, a有什么区别?两者都有效,但我无法解释为什么...

  3. 为什么第 2 步和第 3 步打印的地址不同?

  4. 为什么不能直接修改地址,比如直接把&b赋值给&a

非常感谢您的帮助

  1. From what I understand &x gives the address at which x is stored. To get the actual value of x, I need to use *x. Then, I don't understand why &x is of type *int. As *x and &x are both of type *int, their differences are not clear to me at all.

*int在函数声明中时,表示int指针。但在声明中,*x 不是类型 *int。这意味着取消引用指针。所以:

*a, *b = *b, *a

这意味着,通过取消引用交换值。 函数参数通过复制传递。所以如果你想将值导出给调用者,你需要传递一个变量的指针作为参数。

  1. In my swap function, what is the difference between using *a, *b = *b, *a and using a, b = b, a? Both work but I can't explain why...

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

  1. Why are the addresses different when printed between step 2 and 3?

这不是定义的结果。变量的地址由 go 的运行时处理。

  1. Why can't I just modify the address directly assigning &b to &a for example?

并非不可能。例如:

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
}

使用不安全包,您可以更新地址值。但是不认可。

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 中一样,而无需声明指针变量。有一些歧义,如果这不是你想要的,我不确定这部分答案是否对你有帮助。

与许多 "why can't I..." 问题一样,简单的答案是 "because they defined the language that way"。但是要为什么有点这样,我认为你需要在某些时候(或another type implemented using pointers, like maps or slices)将变量声明为指针,因为指针带有诱杀装置陷阱:例如,您可以在一段代码中以意想不到的方式更改其他代码的局部变量。因此,无论您看到 *int 出现在哪里,它都在告诉您您可能不得不担心(或者,您能够使用)诸如 nil 指针、多段代码的并发访问等问题。

Go 在使指针罩显式化方面比其他语言更为保守:例如,C++ 具有 "reference parameters" (int& i) 的概念,您可以在其中执行 swap(x,y) 没有 & 或指针出现在 main 中。换句话说,在具有引用参数的语言中,您可能必须查看函数的声明才能知道它是否会更改其参数。对于 Go 的人来说,这种行为有点太 surprising/implicit/tricky 了。


所有的引用和取消引用都需要一些思考,你可能只需要花点时间就能搞定;不过希望这一切对您有所帮助。