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() 的执行流程。


代码分析

定义

  1. type N intN 是基于 int 的自定义类型。
  2. value() 方法:
    • 接收者是 N(值传递),意味着 na 的副本。
    • n++ 只修改副本,不影响原始值。
    • 打印 n 的地址和值。
  3. 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(实际地址会因运行环境而异),以下是逐步分析:

  1. 初始化并打印 a

    var a N = 25
    fmt.Printf("a: %p,%v\n", &a, a)
    
    • 输出:a: 0xc0000120b0,25
    • &aa 的地址,a 的值是 25
  2. 定义指针 p

    p := &a
    
    • pa 的地址,即 0xc0000120b0
  3. 调用 a.value()

    func (n N) value() {
        n++
        fmt.Printf("v: %p,%v\n", &n, n)
    }
    
    • a 被值传递给 nna 的副本,地址不同(假设为 0xc0000120b8)。
    • n++ 将副本 n25 改为 26,但不影响 a
    • 输出:v: 0xc0000120b8,26
    • a 保持不变,仍为 25
  4. 调用 a.pointer()

    func (n *N) pointer() {
        *n++
        fmt.Printf("p: %p,%v\n", n, *n)
    }
    
    • a 的地址(0xc0000120b0)传递给 nn 是指针。
    • *n++a 的值从 25 改为 26
    • 输出:p: 0xc0000120b0,26
    • a 现在是 26
  5. 打印 a

    fmt.Println(a)
    
    • 输出:26
    • 此时 a 的值已被 a.pointer() 修改为 26
  6. 调用 p.value()

    • p*N 类型,调用 value() 时,*p(即 a 的值 26)被值传递给 n
    • n 是副本,地址不同(假设为 0xc0000120c0)。
    • n++ 将副本从 26 改为 27,但不影响 a
    • 输出:v: 0xc0000120c0,27
    • a 保持不变,仍为 26
  7. 调用 p.pointer()

    • p*N 类型,直接传递给 nn0xc0000120b0
    • *n++a 的值从 26 改为 27
    • 输出:p: 0xc0000120b0,27
    • a 现在是 27
  8. 最后打印 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

为什么会这样?

  1. 值接收者 vs 指针接收者
    • value() 使用值接收者(n N),传入的是副本,修改不影响原始值。
    • pointer() 使用指针接收者(n *N),传入的是指针,修改影响原始值。
  2. 地址差异
    • 每次值传递(如 a.value()p.value())都会创建副本,副本有新的地址。
    • 指针传递(如 a.pointer()p.pointer())使用的是原始地址。
  3. Go 的方法调用
    • Go 自动处理 *N 类型调用值接收者方法(p.value()),会解引用后传递值的副本。
    • 指针接收者方法直接操作指针指向的内存。

在 Go 编程语言中,“接收者”(receiver)是指定义在方法上的参数,它指定了该方法所属的类型以及方法操作的对象。简单来说,接收者决定了方法是绑定到某个类型上的,并且在方法体内可以通过接收者访问或修改该类型的实例。

在你的代码中,value()pointer() 是定义在类型 N 上的方法,它们的接收者分别是值类型和指针类型。我们来详细解释一下。


接收者的两种形式

  1. 值接收者(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)被复制给 nn++ 修改的是副本 n,而 a 不变。
  2. 指针接收者(Pointer Receiver)

    • 语法:func (receiverName *Type) methodName()
    • 接收者是一个指向类型的指针,方法可以直接修改指针指向的原始值。
    • 示例:
      func (n *N) pointer() {
          *n++
          fmt.Printf("p: %p,%v\n", n, *n)
      }
      
      • n 是类型 *N 的指针接收者。
      • 调用 a.pointer() 时,na 的地址,*n++ 修改的是 a 的值。

接收者的作用

  • 绑定方法:接收者将方法绑定到某个类型(这里是 N),类似于面向对象编程中的“类方法”。
  • 访问实例:方法体内可以通过接收者(n)访问调用该方法的实例(ap)。
  • 修改权限
    • 值接收者:操作副本,无法修改原始值。
    • 指针接收者:操作原始值的地址,可以修改原始值。

为什么需要接收者?

Go 没有传统意义上的“类”,但通过类型和方法的结合,接收者提供了一种类似面向对象的行为。例如:

  • 你可以为自定义类型(如 N)定义方法。
  • 根据接收者的类型(值或指针),控制方法的行为。

代码中的例子

  1. a.value()(值接收者)

    • aN 类型,值传递给 nn 是副本。
    • 修改 n 不影响 a
  2. a.pointer()(指针接收者)

    • a 的地址被传递给 nn*N 类型。
    • *n++ 修改 a 的值。
  3. p.value()(值接收者)

    • p*N 类型,Go 自动解引用 *p(即 a 的值)并传递副本给 n
    • 修改 n 不影响 a
  4. p.pointer()(指针接收者)

    • p*N 类型,直接传递给 nna 的地址。
    • *n++ 修改 a 的值。

值接收者 vs 指针接收者的选择

  • 值接收者:适用于不需修改原始值或类型较小(复制成本低)的情况。
  • 指针接收者:适用于需要修改原始值或类型较大(避免复制开销)的情况。

总结

“接收者”是 Go 中方法的核心概念,它决定了方法如何与类型实例交互。在你的代码中,value()pointer() 的不同行为正是因为接收者类型(值 vs 指针)的区别:

  • 值接收者操作副本,原始值不变。
  • 指针接收者操作原始值,修改生效。
posted @ 2025-03-04 14:20  仁义礼智信的  阅读(32)  评论(0)    收藏  举报