go中接收者
package main
import "fmt"
type N int
func (n N) value() {
n++
fmt.Printf("v: %p,%v\n", &n, n)
}
func (n *N) pointer() {
*n++
fmt.Printf("p: %p,%v\n", n, *n)
}
func main() {
//case1()
case2()
}
func case1() {
var a N = 25
fmt.Printf("a: %p,%v\n", &a, a)
a.value()
fmt.Println(a)
a.pointer()
fmt.Printf("a: %p,%v\n", &a, a)
}
func case2() {
var a N = 25
fmt.Printf("a: %p,%v\n", &a, a)
p := &a
a.value()
a.pointer()
fmt.Println(a)
p.value()
p.pointer()
fmt.Printf("a: %p,%v\n", &a, a)
}
我来分析 case2() 的输出结果,并解释其原因。代码中注释掉了 case1(),所以我们只关注 case2() 的执行流程。
代码分析
定义
type N int:N是基于int的自定义类型。value()方法:- 接收者是
N(值传递),意味着n是a的副本。 n++只修改副本,不影响原始值。- 打印
n的地址和值。
- 接收者是
pointer()方法:- 接收者是
*N(指针传递),意味着n是指向a的指针。 *n++修改指针指向的值,即修改原始值。- 打印指针地址和解引用后的值。
- 接收者是
case2() 执行步骤
func case2() {
var a N = 25
fmt.Printf("a: %p,%v\n", &a, a) // 打印 a 的地址和值
p := &a // p 是 a 的指针
a.value() // 值接收者,a 的副本被修改
a.pointer() // 指针接收者,修改 a 的值
fmt.Println(a) // 打印 a 的值
p.value() // 值接收者,p 解引用后的副本被修改
p.pointer() // 指针接收者,修改 p 指向的 a 的值
fmt.Printf("a: %p,%v\n", &a, a) // 再次打印 a 的地址和值
}
输出结果和逐步解释
假设变量 a 的内存地址为 0xc0000120b0(实际地址会因运行环境而异),以下是逐步分析:
-
初始化并打印
a:var a N = 25 fmt.Printf("a: %p,%v\n", &a, a)- 输出:
a: 0xc0000120b0,25 &a是a的地址,a的值是25。
- 输出:
-
定义指针
p:p := &ap是a的地址,即0xc0000120b0。
-
调用
a.value():func (n N) value() { n++ fmt.Printf("v: %p,%v\n", &n, n) }a被值传递给n,n是a的副本,地址不同(假设为0xc0000120b8)。n++将副本n从25改为26,但不影响a。- 输出:
v: 0xc0000120b8,26 a保持不变,仍为25。
-
调用
a.pointer():func (n *N) pointer() { *n++ fmt.Printf("p: %p,%v\n", n, *n) }a的地址(0xc0000120b0)传递给n,n是指针。*n++将a的值从25改为26。- 输出:
p: 0xc0000120b0,26 a现在是26。
-
打印
a:fmt.Println(a)- 输出:
26 - 此时
a的值已被a.pointer()修改为26。
- 输出:
-
调用
p.value():p是*N类型,调用value()时,*p(即a的值26)被值传递给n。n是副本,地址不同(假设为0xc0000120c0)。n++将副本从26改为27,但不影响a。- 输出:
v: 0xc0000120c0,27 a保持不变,仍为26。
-
调用
p.pointer():p是*N类型,直接传递给n,n是0xc0000120b0。*n++将a的值从26改为27。- 输出:
p: 0xc0000120b0,27 a现在是27。
-
最后打印
a:fmt.Printf("a: %p,%v\n", &a, a)- 输出:
a: 0xc0000120b0,27 a的地址不变,值被修改为27。
- 输出:
完整输出
a: 0xc0000120b0,25
v: 0xc0000120b8,26
p: 0xc0000120b0,26
26
v: 0xc0000120c0,27
p: 0xc0000120b0,27
a: 0xc0000120b0,27
为什么会这样?
- 值接收者 vs 指针接收者:
value()使用值接收者(n N),传入的是副本,修改不影响原始值。pointer()使用指针接收者(n *N),传入的是指针,修改影响原始值。
- 地址差异:
- 每次值传递(如
a.value()和p.value())都会创建副本,副本有新的地址。 - 指针传递(如
a.pointer()和p.pointer())使用的是原始地址。
- 每次值传递(如
- Go 的方法调用:
- Go 自动处理
*N类型调用值接收者方法(p.value()),会解引用后传递值的副本。 - 指针接收者方法直接操作指针指向的内存。
- Go 自动处理
在 Go 编程语言中,“接收者”(receiver)是指定义在方法上的参数,它指定了该方法所属的类型以及方法操作的对象。简单来说,接收者决定了方法是绑定到某个类型上的,并且在方法体内可以通过接收者访问或修改该类型的实例。
在你的代码中,value() 和 pointer() 是定义在类型 N 上的方法,它们的接收者分别是值类型和指针类型。我们来详细解释一下。
接收者的两种形式
-
值接收者(Value Receiver)
- 语法:
func (receiverName Type) methodName() - 接收者是一个值的副本,方法操作的是这个副本,原始值不会被修改。
- 示例:
func (n N) value() { n++ fmt.Printf("v: %p,%v\n", &n, n) }n是类型N的值接收者。- 调用
a.value()时,a的值(25)被复制给n,n++修改的是副本n,而a不变。
- 语法:
-
指针接收者(Pointer Receiver)
- 语法:
func (receiverName *Type) methodName() - 接收者是一个指向类型的指针,方法可以直接修改指针指向的原始值。
- 示例:
func (n *N) pointer() { *n++ fmt.Printf("p: %p,%v\n", n, *n) }n是类型*N的指针接收者。- 调用
a.pointer()时,n是a的地址,*n++修改的是a的值。
- 语法:
接收者的作用
- 绑定方法:接收者将方法绑定到某个类型(这里是
N),类似于面向对象编程中的“类方法”。 - 访问实例:方法体内可以通过接收者(
n)访问调用该方法的实例(a或p)。 - 修改权限:
- 值接收者:操作副本,无法修改原始值。
- 指针接收者:操作原始值的地址,可以修改原始值。
为什么需要接收者?
Go 没有传统意义上的“类”,但通过类型和方法的结合,接收者提供了一种类似面向对象的行为。例如:
- 你可以为自定义类型(如
N)定义方法。 - 根据接收者的类型(值或指针),控制方法的行为。
代码中的例子
-
a.value()(值接收者):a是N类型,值传递给n,n是副本。- 修改
n不影响a。
-
a.pointer()(指针接收者):a的地址被传递给n,n是*N类型。*n++修改a的值。
-
p.value()(值接收者):p是*N类型,Go 自动解引用*p(即a的值)并传递副本给n。- 修改
n不影响a。
-
p.pointer()(指针接收者):p是*N类型,直接传递给n,n是a的地址。*n++修改a的值。
值接收者 vs 指针接收者的选择
- 值接收者:适用于不需修改原始值或类型较小(复制成本低)的情况。
- 指针接收者:适用于需要修改原始值或类型较大(避免复制开销)的情况。
总结
“接收者”是 Go 中方法的核心概念,它决定了方法如何与类型实例交互。在你的代码中,value() 和 pointer() 的不同行为正是因为接收者类型(值 vs 指针)的区别:
- 值接收者操作副本,原始值不变。
- 指针接收者操作原始值,修改生效。

浙公网安备 33010602011771号