go 解指针

下面的这个代码没看懂, f2 := (*N).test,解释一下
package main

import "fmt"

type N int

func main() {
case1()
}

func case1() {
var n N = 25
fmt.Printf("main.n:%p,%v\n", &n, n)

n.test()

f1 := N.test
f1(n)

f2 := (*N).test
f2(&n)

}

func (n N) test() {
fmt.Printf("test.n:%p,%v\n", &n, n)
}

你的问题聚焦在代码中的 f2 := (*N).test 这一行,以及它在 Go 中的含义和使用方式。让我逐步拆解这段代码,详细解释它的行为和背后的机制。


完整代码

先看完整代码:

package main

import "fmt"

type N int

func main() {
	case1()
}

func case1() {
	var n N = 25
	fmt.Printf("main.n:%p,%v\n", &n, n) // 输出 n 的地址和值

	n.test()       // 方法调用

	f1 := N.test   // 方法表达式,绑定到类型 N
	f1(n)          // 调用 f1,传入 n

	f2 := (*N).test // 方法表达式,绑定到类型 *N
	f2(&n)         // 调用 f2,传入 &n
}

func (n N) test() {
	fmt.Printf("test.n:%p,%v\n", &n, n) // 输出 n 的地址和值
}

运行结果(示例输出,地址会因运行环境变化)

main.n:0xc0000140b8,25
test.n:0xc0000140c0,25
test.n:0xc0000140c8,25
test.n:0xc0000140b8,25

逐步解释

1. type N int(n N) test()

  • N 是一个自定义类型,基于 int
  • (n N) test() 定义了一个方法 test,它的接收者是 N 类型的值(值接收者)。
  • 在 Go 中,方法的接收者可以是值类型(如 N)或指针类型(如 *N)。这里是值接收者,意味着调用 test 时,接收者 n 是调用者的副本。

2. n.test()

  • var n N = 25 定义了一个变量 n,类型是 N,值为 25。
  • n.test() 是直接调用 test 方法。
  • 因为 test 的接收者是值类型 N,Go 会将 n 的值复制一份传递给 test
  • 输出:
    • main.n:0xc0000140b8,25n 的地址和值。
    • test.n:0xc0000140c0,25test 中的 n 是副本,地址不同,但值相同。

3. f1 := N.test

  • N.test 是一种方法表达式(Method Expression)。
  • 在 Go 中,T.methodT 是类型)会将方法 test 转换为一个函数,第一个参数是接收者类型 T
  • 这里 N.test 等价于一个函数签名:func(n N)
  • f1(n) 调用这个函数,显式传入 n 作为接收者。
  • 因为接收者是值类型,n 再次被复制。
  • 输出:test.n:0xc0000140c8,25,地址又不同,因为是新的副本。

4. f2 := (*N).test

  • (*N).test 也是方法表达式,但接收者类型是 *N(指向 N 的指针)。
  • 尽管 test 方法的定义是 (n N)(值接收者),Go 允许通过指针类型 *N 调用值接收者方法。
  • 当通过 *N 调用时,Go 会自动解引用指针,将值传递给 test
  • f2 的类型等价于 func(n *N),需要传入一个 *N 类型的参数。
  • f2(&n) 调用时,传入 n 的地址(&n),test 方法内部会解引用这个地址获取值。
  • 输出:test.n:0xc0000140b8,25,地址与 main.n 相同,因为解引用后直接使用了原始的 n

重点:f2 := (*N).test 的含义

  • (*N).test 表示将 test 方法绑定到 *N 类型,而不是 N 类型。
  • 在 Go 中,值接收者方法(如 (n N) test())可以通过值类型(如 N)或指针类型(如 *N)调用:
    • 如果是值类型调用(n.test()f1(n)),会复制值。
    • 如果是指针类型调用(f2(&n)),会传入指针,方法内部解引用访问原始值。
  • f2 的函数签名是 func(n *N),需要显式传入一个 *N 参数(&n),而不是 N

为什么地址相同?

  • f2(&n) 中,&n 是原始变量 n 的地址(例如 0xc0000140b8)。
  • test 方法接收到这个指针后,解引用得到值 25,但 &n 仍然指向原始变量的地址。
  • 因此,fmt.Printf("test.n:%p,%v\n", &n, n) 中的 &n 是原始变量的地址,而不是副本。

f1 的区别

  • f1 := N.test:接收者是值类型 N,调用时总是复制 n,所以 test 内的 &n 是副本地址。
  • f2 := (*N).test:接收者是指针类型 *N,调用时传入地址,test 解引用后操作的是原始数据。

输出分析

main.n:0xc0000140b8,25       // 原始 n 的地址和值
test.n:0xc0000140c0,25       // n.test(),值复制,新地址
test.n:0xc0000140c8,25       // f1(n),值复制,新地址
test.n:0xc0000140b8,25       // f2(&n),指针解引用,原始地址
  • 前两次调用(n.test()f1(n))是值传递,test 内的 n 是副本。
  • 最后一次调用(f2(&n))是指针传递,test 操作的是原始 n 的地址。

总结

  • f2 := (*N).test
    • 表示将 test 方法绑定到 *N 类型,生成一个函数 func(n *N)
    • 调用时需要传入 *N 类型参数(如 &n)。
    • 方法内部会解引用指针,访问原始值。
  • f1 := N.test 的区别
    • f1 需要值类型 N,总是复制接收者。
    • f2 需要指针类型 *N,操作原始数据的地址。
  • Go 的灵活性
    • 值接收者方法可以通过值或指针调用,指针调用时会自动解引用。

如果你还有疑问或想深入某个部分,随时告诉我!

posted @ 2025-03-05 14:15  仁义礼智信的  阅读(18)  评论(0)    收藏  举报