理解Go语言中的指针

huangapple go评论76阅读模式
英文:

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. 在我的 swap 函数中,使用 *a, *b = *b, *a 和使用 a, b = b, a 有什么区别?两者都可以工作,但我无法解释为什么...

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

  4. 为什么我不能直接将 &b 赋值给 &a 来修改地址?

非常感谢你的帮助。

英文:

I am trying to understand how pointers work in Go. On a general level, I have little experience with pointers as I mostly use javascript.

I wrote this dummy program:

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)
}

Which prints the following results:

$ go run test.go
1 15 2
2 0x208178170 0x208178178
3 0x2081ac020 0x2081ac028
4 0x208178178 0x208178170
5 2 15

I have several questions:

  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.

  2. 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...

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

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

Many thanks for your help

答案1

得分: 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 2

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

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

对于许多“为什么我不能……”的问题,简单的答案是“因为他们以这种方式定义了语言”。但是稍微解释一下为什么会这样,我认为你需要在某个地方将变量声明为指针(或者使用指针实现的其他类型,如映射或切片),因为指针带来了一些陷阱:你可以在一段代码中做一些改变其他代码的局部变量的操作,这可能会导致意外的结果。所以无论你在哪里看到*int出现,它都在告诉你可能需要担心(或者你可以使用)nil指针、多个代码片段的并发访问等问题。

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

无论如何,所有的引用和解引用都需要一些思考,你可能需要一段时间来适应它;希望这些都有所帮助。

英文:
  1. It's a confusion about the language that leads you to think *x and &x are the same type. As Not_a_Golfer pointed out, the uses of * in expressions and type names are different. *x in your main() is invalid syntax, because * in an expression tries to get the value pointed to by the pointer that follows, but x is not a pointer (it's an int).

I think you were thinking of the fact that when you take a pointer to x using &x, the character you add to the type name is * to form *int. I can see how it's confusing that &var gets you *typ rather than &typ. On the other hand, if they'd put & on the type name instead, that would be confusing in other situations. Some trickiness is inevitable, and like human languages, it may be easier to learn by using than discussion alone.

  1. Again, turns out the assumption is inaccurate: a, b = b, a swaps the pointers that the swap function looks at, but doesn't swap the values from main's perspective and the last line of output changes to 5 15 2: http://play.golang.org/p/rCXDgkZ9kG

  2. swap is printing the address of the pointer variables, not of the underlying integers. You'd print a and b to see the addresses of the integers.

  3. I'm going to assume, maybe wrongly, that you were hoping you could swap the locations that arbitrary variables point to with & syntax, as in &x, &y = &y, &x, without ever declaring pointer variables. There's some ambiguity, and if that's not what you were going for, I'm not sure if this part of the answer will help you.

As with many "why can't I..." questions the easy out is "because they defined the language that way". But going to why it's that way a little bit, I think you're required to declare a variable as a pointer at some point (or another type implemented using pointers, like maps or slices) because pointers come with booby traps: you can do something over in one piece of code that changes other code's local variables in unexpected ways, for example. So wherever you see *int appear, it's telling you you might have to worry about (or, that you're able to use) things like nil pointers, concurrent access from multiple pieces of code, etc.

Go is a bit more conservative about making pointer-hood explicit than other languages are: C++, for example, has the concept of "reference parameters" (int& i) where you could do your swap(x,y) with no & or pointers appearing in main. In other words, in languages with reference parameters, you might have to look at the declaration of a function to know whether it will change its arguments. That sort of behavior was a little too surprising/implicit/tricky for the Go folks to adopt.


No getting around that all the referencing and dereferencing takes some thinking, and you might just have to work with it a while to get it; hope all this helps, though.

答案2

得分: 1

  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] // 取 arr 的第一个元素的地址

	println(*pi) // 打印 2

	// 将指针加上 4 字节
	pi = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(pi)) + unsafe.Sizeof(int(0))))

	println(*pi) // 打印 3
}

使用 unsafe 包,你可以更新地址的值。但这并不被推荐使用。

英文:

> 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.

When *int is in function declaration, it mean pointer of int. but it's in the statement, *x is not type *int. it mean dereference of the pointer. So:

*a, *b = *b, *a

This mean, swapping values by dereference.
The fuction arguments is passing by copyed. So if you want to export values to caller, you need to pass a pointer of the variable as argument.

> 2. 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...

As I said, a, b = b, a mean swapping pointers not swapping values.

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

This is not a defined result. Address of variable are disposed by go's runtime.

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

Not impossible. 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
}

Using unsafe package, you can update address values. But it's not endorsed.

huangapple
  • 本文由 发表于 2015年1月8日 07:33:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/27830605.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定