解释下面的代码,输出结果以及为什么

package main

import "fmt"

type N int

func main() {
case1()
fmt.Println()
case2()
fmt.Println()
case3()
}

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 case2() {
n := N(25)

p := &n

n++
f1 := n.test

n++
f2 := p.test

n++
fmt.Printf("main.n:%p,%v\n", &n, n)

f1()
f2()

}

func case3() {
n := N(25)

p := &n

n++
f1 := n.test1

n++
f2 := p.test1

n++
fmt.Printf("main.n:%p,%v\n", &n, n)

f1()
f2()

}

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

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

我将逐步解释这段 Go 代码,分析其结构、方法接收者类型(值 vs 指针)以及输出背后的原因。


代码结构概览

  1. 类型定义

    • type N int 定义了一个基于 int 的新类型 N
  2. 方法

    • func (n N) test():一个值接收者方法,接收 N 的副本。
    • func (n *N) test1():一个指针接收者方法,接收指向 N 的指针。
  3. 三个测试用例

    • case1():展示了方法调用和函数值的不同方式。
    • case2():测试值接收者方法的行为。
    • case3():测试指针接收者方法的行为。
  4. 输出

    • 使用 fmt.Printf 打印变量的地址(%p)和值(%v)。

详细分析

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

执行过程

  1. var n N = 25

    • 创建变量 n,类型为 N,值为 25。
    • &nn 的内存地址。
  2. fmt.Printf("main.n:%p,%v\n", &n, n)

    • 打印 n 的地址和值,例如:main.n:0xc0000b4018,25
  3. n.test()

    • 调用值接收者方法 test()
    • Go 会将 n 的副本传递给 test()
    • test() 中,&n 是副本的新地址,值为 25。
    • 输出类似:test.n:0xc0000b4020,25
    • 注意:地址与 main.n 不同,因为是副本。
  4. f1 := N.testf1(n)

    • N.test 是方法表达式,类型为 func(N)
    • f1(n) 显式地将 n 传递给 test(),行为与 n.test() 相同。
    • 输出类似:test.n:0xc0000b4028,25(新副本地址)。
  5. f2 := (*N).testf2(&n)

    • (*N).test 是指针类型的方法表达式,但 test() 是值接收者方法。
    • Go 自动解引用 &n,传递值的副本。
    • 输出类似:test.n:0xc0000b4030,25(新副本地址)。

小结

  • 值接收者方法每次调用都会创建接收者的副本,因此地址不同。
  • 方法可以直接调用(n.test())或通过方法表达式调用(N.test(n)(*N).test(&n))。

case2(): 值接收者方法的行为

func case2() {
	n := N(25)
	p := &n

	n++
	f1 := n.test

	n++
	f2 := p.test

	n++
	fmt.Printf("main.n:%p,%v\n", &n, n)

	f1()
	f2()
}

执行过程

  1. n := N(25)p := &n

    • n 初始化为 25,pn 的指针。
  2. n++

    • n 增加到 26。
  3. f1 := n.test

    • n.test 是方法值,捕获当前 n 的副本(26)。
    • f1 的类型是 func(),绑定了 n 的值 26。
  4. n++

    • n 增加到 27。
  5. f2 := p.test

    • p.test 等价于 (*p).test,捕获 n 的副本(27)。
    • f2 绑定了值 27。
  6. n++

    • n 增加到 28。
  7. fmt.Printf("main.n:%p,%v\n", &n, n)

    • 打印 n 的地址和值,例如:main.n:0xc0000b4018,28
  8. f1()

    • 调用时使用绑定值 26。
    • 输出类似:test.n:0xc0000b4020,26
  9. f2()

    • 调用时使用绑定值 27。
    • 输出类似:test.n:0xc0000b4028,27

小结

  • 值接收者方法通过方法值捕获变量的副本。
  • 后续对 n 的修改不影响 f1f2,因为它们绑定的是调用时的值。

case3(): 指针接收者方法的行为

func case3() {
	n := N(25)
	p := &n

	n++
	f1 := n.test1

	n++
	f2 := p.test1

	n++
	fmt.Printf("main.n:%p,%v\n", &n, n)

	f1()
	f2()
}

执行过程

  1. n := N(25)p := &n

    • 同上。
  2. n++

    • n 增加到 26。
  3. f1 := n.test1

    • test1 是指针接收者方法。
    • n.test1 等价于 (&n).test1f1 绑定 n 的地址(&n)。
    • f1func() 类型,记录了指针。
  4. n++

    • n 增加到 27。
  5. f2 := p.test1

    • p.test1 直接使用指针 p(即 &n)。
    • f2 也绑定 n 的地址。
  6. n++

    • n 增加到 28。
  7. fmt.Printf("main.n:%p,%v\n", &n, n)

    • 输出类似:main.n:0xc0000b4018,28
  8. f1()

    • 使用绑定指针调用 test1,此时 *(&n) 是 28。
    • 输出类似:test1.n:0xc0000b4018,28
  9. f2()

    • 同上,地址和值相同:test1.n:0xc0000b4018,28

小结

  • 指针接收者方法通过方法值绑定变量的地址。
  • 后续对 n 的修改会反映在 f1f2 的调用中,因为它们操作的是同一地址。

值接收者 vs 指针接收者的区别

  1. 值接收者 (test)

    • 接收值的副本,调用时与原始变量独立。
    • 方法值(n.test)捕获调用时的值。
  2. 指针接收者 (test1)

    • 接收变量的地址,操作原始数据。
    • 方法值(n.test1p.test1)捕获地址,反映最新值。

为什么这样设计?

  • 值接收者:用于不可变操作或小型数据结构,避免修改原始值,保持安全性。
  • 指针接收者:用于需要修改原始值或处理大数据结构(避免复制开销)的情况。
  • Go 的方法值机制允许灵活地将方法作为函数使用,同时保留接收者语义。

希望这个解释清楚地阐明了代码的行为!如果有进一步问题,请告诉我。

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