返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

6. 方法

发布于 2024-10-12 19:15:48 字数 3269 浏览 0 评论 0 收藏 0

方法(method)是与对象实例(instance)相绑定的特殊函数。

方法是面向对象编程的基本概念,用于维护和展示对象自身状态。对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来对外暴露。普通函数专注于算法流程,接收参数完成逻辑运算,返回结果并清理现场。也就是说,方法有持续性状态,而函数通常没有。

  • 前置接收参数(receiver),代表方法所属类型。
  • 可为当前包内除接口和指针以外的任何类型定义方法。
  • 不支持静态方法(static method)或关联函数。
  • 不支持重载(overload)。
func (int) test() {}
//    ~~~ cannot define new methods on non-local type int

// -------------------------

type N *int
func (N) test() {}
//    ~ invalid receiver type N (pointer or interface type)

// -------------------------

type M int
func  (M) test(){}
func (*M) test(){}
//        ~~~~ redeclared in this block

对接收参数命名无限制,按惯例选用简短有意义的名称。

如方法内部不引用实例,可省略接收参数名,仅留类型。

type N int

func (n N) toString() string {
	return fmt.Sprintf("%#x", n)
}

func (N) test() {      // 省略接收参数名。
	println("test")
}

// -----------------------------

func main() {
	var a N = 25
	println(a.toString())
}

接收参数可以是指针类型,调用时据此决定是否复制(pass by value)。

注意区别:

不能为指针和接口定义方法,是说类型 N 本身不能是接口和指针。

这与作为参数列表成员的 receiver *N 意思完全不同。

方法本质上就是特殊函数,接收参数无非是其第一参数。

只不过,在某些语言里它是隐式的 this

type N int

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

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

// -----------------------------

func main() {
	var a N = 25
	fmt.Printf("%p\n", &a)  // 0xc000014080

	a.copy()                // 0xc000014088, 25
	N.copy(a)               // 0xc0000140a0, 25

	a++
    
	a.ref()                 // 0xc000014080, 26
	(*N).ref(&a)            // 0xc000014080, 26
}

编译器根据接收参数类型,自动在值和指针间转换。

type N int
func (n N) copy() {}
func (n *N) ref() {}

//---------------------

func main() {
	var a N = 25
	var p *N = &a

	a.copy()
    a.ref()     // (*N).ref(&a)

    p.copy()    // N.copy(*p)
	p.ref()
}

/*

$ go build -gcflags "-N -l"
$ go tool objdump -S -s "main\.main" ./test

TEXT main.main(SB)
func main() {
        var a N = 25
  0x455254              MOVQ $0x19, 0x8(SP)     
        var p *N = &a
  0x45525d              LEAQ 0x8(SP), CX        
  0x455262              MOVQ CX, 0x18(SP)       
        a.copy()
  0x455267              MOVQ 0x8(SP), AX        
  0x45526c              CALL main.N.copy(SB)    
        a.ref()
  0x455271              LEAQ 0x8(SP), AX        ; &a
  0x455276              CALL main.(*N).ref(SB)  
        p.copy()
  0x45527b              MOVQ 0x18(SP), CX       
  0x455282              MOVQ 0(CX), AX          
  0x45528a              CALL main.N.copy(SB)    
        p.ref()
  0x45528f              MOVQ 0x18(SP), AX       
  0x455294              CALL main.(*N).ref(SB)  
}

*/

不能以多级指针调用方法。

func main() {
	var a N = 25
	var p *N = &a

	p2 := &p

	// p2.copy()
	// ~~~~~~~ p2.copy undefined

	// p2.ref()
	// ~~~~~~ p2.ref undefined

	(*p2).copy()
	(*p2).ref()
}

如何确定接收参数(receiver)类型?

  • 修改实例状态,用 *T
  • 不修改状态的小对象或固定值,用 T
  • 大对象用 *T ,减少复制成本。
  • 引用类型、字符串、函数等指针包装对象,用 T
  • Mutex 等同步字段,用 *T ,避免因复制造成锁无效。
  • 其他无法确定的,都用 *T

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文