解释下面的代码,输出结果以及为什么
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 指针)以及输出背后的原因。
代码结构概览
-
类型定义:
type N int定义了一个基于int的新类型N。
-
方法:
func (n N) test():一个值接收者方法,接收N的副本。func (n *N) test1():一个指针接收者方法,接收指向N的指针。
-
三个测试用例:
case1():展示了方法调用和函数值的不同方式。case2():测试值接收者方法的行为。case3():测试指针接收者方法的行为。
-
输出:
- 使用
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)
}
执行过程:
-
var n N = 25:- 创建变量
n,类型为N,值为 25。 &n是n的内存地址。
- 创建变量
-
fmt.Printf("main.n:%p,%v\n", &n, n):- 打印
n的地址和值,例如:main.n:0xc0000b4018,25。
- 打印
-
n.test():- 调用值接收者方法
test()。 - Go 会将
n的副本传递给test()。 - 在
test()中,&n是副本的新地址,值为 25。 - 输出类似:
test.n:0xc0000b4020,25。 - 注意:地址与
main.n不同,因为是副本。
- 调用值接收者方法
-
f1 := N.test和f1(n):N.test是方法表达式,类型为func(N)。f1(n)显式地将n传递给test(),行为与n.test()相同。- 输出类似:
test.n:0xc0000b4028,25(新副本地址)。
-
f2 := (*N).test和f2(&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()
}
执行过程:
-
n := N(25)和p := &n:n初始化为 25,p是n的指针。
-
n++:n增加到 26。
-
f1 := n.test:n.test是方法值,捕获当前n的副本(26)。f1的类型是func(),绑定了n的值 26。
-
n++:n增加到 27。
-
f2 := p.test:p.test等价于(*p).test,捕获n的副本(27)。f2绑定了值 27。
-
n++:n增加到 28。
-
fmt.Printf("main.n:%p,%v\n", &n, n):- 打印
n的地址和值,例如:main.n:0xc0000b4018,28。
- 打印
-
f1():- 调用时使用绑定值 26。
- 输出类似:
test.n:0xc0000b4020,26。
-
f2():- 调用时使用绑定值 27。
- 输出类似:
test.n:0xc0000b4028,27。
小结:
- 值接收者方法通过方法值捕获变量的副本。
- 后续对
n的修改不影响f1和f2,因为它们绑定的是调用时的值。
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()
}
执行过程:
-
n := N(25)和p := &n:- 同上。
-
n++:n增加到 26。
-
f1 := n.test1:test1是指针接收者方法。n.test1等价于(&n).test1,f1绑定n的地址(&n)。f1是func()类型,记录了指针。
-
n++:n增加到 27。
-
f2 := p.test1:p.test1直接使用指针p(即&n)。f2也绑定n的地址。
-
n++:n增加到 28。
-
fmt.Printf("main.n:%p,%v\n", &n, n):- 输出类似:
main.n:0xc0000b4018,28。
- 输出类似:
-
f1():- 使用绑定指针调用
test1,此时*(&n)是 28。 - 输出类似:
test1.n:0xc0000b4018,28。
- 使用绑定指针调用
-
f2():- 同上,地址和值相同:
test1.n:0xc0000b4018,28。
- 同上,地址和值相同:
小结:
- 指针接收者方法通过方法值绑定变量的地址。
- 后续对
n的修改会反映在f1和f2的调用中,因为它们操作的是同一地址。
值接收者 vs 指针接收者的区别
-
值接收者 (
test):- 接收值的副本,调用时与原始变量独立。
- 方法值(
n.test)捕获调用时的值。
-
指针接收者 (
test1):- 接收变量的地址,操作原始数据。
- 方法值(
n.test1或p.test1)捕获地址,反映最新值。
为什么这样设计?
- 值接收者:用于不可变操作或小型数据结构,避免修改原始值,保持安全性。
- 指针接收者:用于需要修改原始值或处理大数据结构(避免复制开销)的情况。
- Go 的方法值机制允许灵活地将方法作为函数使用,同时保留接收者语义。
希望这个解释清楚地阐明了代码的行为!如果有进一步问题,请告诉我。

浙公网安备 33010602011771号