方法是与对象实例绑定的特殊函数。
方法是面向对象编程的基本概念,用于维护和展示对象的自身状态。
对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来暴露对外通信接口。
普通函数则专注于算法流程,通过接收参数来完成特定逻辑算法,并最终返回结果。
换句话说,方法是有关联状态的,而函数通常没有。

方法和函数定义语法区别在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。
在某些语言里,尽管没有显式定义,但会在调用时隐式传递this实例参数。

可以为当前包以及除接口和指针之外的任何类型定义方法。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int //自定义类型
  6.  
  7. func (n N) toString() string { //方法本质上就是绑定在某个实例上的函数,与函数相比就是多了一个接收参数,来说明方法所属。
  8. return fmt.Sprintf("%#x", n)
  9. }
  10.  
  11. func main() {
  12. var a N = 25 //调用方法必须有实例对象
  13. fmt.Println(a.toString()) //0x19
  14. }

方法同样不支持重载(overload)。receiver参数名没有限制,按惯例会选用简短有意义的名称。
如果方法内部并不引用实例,可省略参数名,仅保留类型。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int
  6.  
  7. func (N) test() {
  8. fmt.Println("hello, world")
  9. }
  10.  
  11. func main() {
  12. var n N = 3
  13. n.test()
  14. }

方法可以看作特殊函数,那么receiver的类型自然可以看作是基础类型或指针类型。
这会关系到调用时对象实例是否被复制。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int
  6.  
  7. func (n N) value() {
  8. n++
  9. fmt.Printf("v: %p, %v\n", &n, n)
  10. }
  11.  
  12. func (n *N) pointer() { //指针类型
  13. (*n)++ //通过指针反取得到数据
  14. fmt.Printf("p: %p, %v\n", n, *n)
  15. }
  16.  
  17. func main() {
  18. var a N = 10
  19.  
  20. a.value() //值传递,复制
  21. a.pointer() //指针引用传递,共用
  22.  
  23. fmt.Printf("a: %p, %v\n", &a, a)
  24. }
  25.  
  26. /*
  27. v: 0xc00004e088, 11
  28. p: 0xc00004e080, 11 //通过指针传值,其实是指向原数据
  29. a: 0xc00004e080, 11
  30. */

(n N)与(n *N):定义的是n N,那么只能说明指针是一个值的基础数据。

可使用实例值或指针调用方法,编译器会根据方法的receiver类型自动在基础类型和指针类型间转换。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int
  6.  
  7. func (n N) value() {
  8. n++
  9. fmt.Printf("v: %p, %v\n", &n, n)
  10. }
  11.  
  12. func (n *N) pointer() {
  13. (*n)++
  14. fmt.Printf("p: %p, %v\n", n, *n)
  15. }
  16.  
  17. func main() {
  18. var a N = 10
  19. p := &a
  20.  
  21. fmt.Printf("a: %p, %v\n", &a, a)
  22. a.value()
  23. a.pointer() //这里虽然传递的是整数类型,但是编译器会自动判断
  24.  
  25. p.value()
  26. p.pointer()
  27. fmt.Printf("a: %p, %v\n", &a, a)
  28. }
  29.  
  30. /*
  31. a: 0xc00004e080, 10
  32.  
  33. v: 0xc00004e098, 11 //复制值,+1,并没有改变原数据,变量指向了另一个内存地址
  34. p: 0xc00004e080, 11 //指针传递,+1,改变了元数据
  35.  
  36. v: 0xc00004e0d0, 12 //11 + 1
  37. p: 0xc00004e080, 12 //11 + 1
  38.  
  39. a: 0xc00004e080, 12 //使用指针传递的时候,值改变并不会改变变量的内存地址
  40. */

需要注意的是不能用多层指针调用方法。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int
  6.  
  7. func (n N) value() {
  8. n++
  9. fmt.Printf("v: %p, %v\n", &n, n)
  10. }
  11.  
  12. func (n *N) pointer() {
  13. (*n)++
  14. fmt.Printf("p: %p, %v\n", n, *n)
  15. }
  16.  
  17. func main() {
  18. var a N = 25
  19.  
  20. p := &a
  21. p2 := &p
  22.  
  23. p2.value() //calling method value with receiver p2 (type **N) requires explicit dereference
  24. p2.pointer()
  25. }

指针类型的receiver必须是合法指针(包括nil),或能获取实例地址。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type X struct{}
  6.  
  7. func (x, *X) test() {
  8. fmt.Println("hi!")
  9. }
  10.  
  11. func main() {
  12. var a *X
  13. X{}.test() //结果体没有指针属性
  14.  
  15. }

可以像访问匿名字段成员那样调用方法,由编译器负责查找。

  1. package main
  2.  
  3. import (
  4. "sync"
  5. )
  6.  
  7. type data struct {
  8. sync.Mutex
  9. buf [1024]byte
  10. }
  11.  
  12. func main() {
  13. d := data{}
  14. d.Lock() //直接调用结构体字段的方法
  15. defer d.Unlock()
  16. }

方法也会有同名遮蔽问题。但利用这种特性,可实现类似覆盖操作。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type user struct{}
  6.  
  7. type manager struct {
  8. user
  9. }
  10.  
  11. func (user) toString() string {
  12. return "user"
  13. }
  14.  
  15. func (m manager) toString() string {
  16. return m.user.toString() + ",manager"
  17. }
  18.  
  19. func main() {
  20. var m manager
  21. fmt.Println(m.toString()) //user,manager
  22. fmt.Println(m.user.toString()) //user
  23. }

尽管可以直接访问匿名字段的成员和方法,但是它们依然不属于继承关系。

类型有一个与之相关的方法集,这决定了它是否实现了某个接口
  类型T方法集包含所有receiver T方法
  类型*T方法集包含所有receiver T + *T方法
  匿名嵌入S,T方法集包含所有receiver S方法
  匿名嵌入*S, T方法集包含所有receiver S + *S方法
  匿名嵌入S或*S, *T方法集包含所有receiver S + *S

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "reflect"
  6. )
  7.  
  8. type S struct{}
  9.  
  10. type T struct {
  11. S
  12. }
  13.  
  14. func (S) sVal() {}
  15. func (*S) sPtr() {}
  16. func (T) tVal() {}
  17. func (*S) tPtr() {}
  18.  
  19. func methodSet(a interface{}) {
  20. t := reflect.TypeOf(a)
  21.  
  22. for i, n := 0, t.NumMethod(); i < n; i++ {
  23. m := t.Method(i)
  24. fmt.Println(m.Name, m.Type)
  25. }
  26. }
  27.  
  28. func main() {
  29. var t T
  30. methodSet(t)
  31. fmt.Println("-------------")
  32. methodSet(&t)
  33. }

方法集仅影响接口实现和方法表达式转换,与通过实例或实例指针调用方法无关。
实例并不使用方法集,而是直接调用(或通过隐式字段名。

面向对象的三大特征“封装”、“继承”、“多态”,go语言仅实现了部分特征,它更倾向于“组合大于继承”这种思想。
将模块分解成相互独立的更小单元,分别处理不同方面的需求,最后以匿名嵌入组合到一起。
而其简短一致的调用方式,更是隐藏了内部实现细节

没有父子组合依赖,不会破坏封装。且整体和局部松耦合,可任意增加来实现扩展。
各单元持有单一责任,互无关联,实现和维护更加简单。

方法和函数一样,除直接调用外,还可以赋值给变量,或作为参数传递。
依照具体引用的方式不同,可分为expression和value两种状态。

通用类型引用的method expression会被还原成普通函数样式,receiver是第一参数,调用时须显式传参。
至于类型,可以是T或*T,只要目标方法存在于该类型方法集中即可。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int
  6.  
  7. func (n N) test() {
  8. fmt.Printf("test.n: %p, %d\n", &n, n)
  9. }
  10.  
  11. func main() {
  12. var n N = 25
  13. fmt.Printf("main.n: %p, %d\n", &n, n)
  14.  
  15. f1 := N.test //直接将方法赋值给变量
  16. f1(n)
  17.  
  18. f2 := (*N).test
  19. f2(&n)
  20.  
  21. N.test(n) //直接以表达式方式调用
  22. (*N).test(&n)
  23. }
  24.  
  25. /*
  26. main.n: 0xc00004e080, 25
  27. test.n: 0xc00004e098, 25
  28. test.n: 0xc00004e0b8, 25
  29. */

基于实例或指针引用的method value,参数签名不不会改变,依旧按正常方式调用。
但当method value被赋值给变量或作为参数传递时,会立即计算并赋值该方法执行所需的receiver对象
与其绑定,以便在稍后执行,能隐式传入receiver参数。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int
  6.  
  7. func (n N) test() {
  8. fmt.Printf("test.n: %p, %v\n", &n, n)
  9. }
  10.  
  11. func main() {
  12. var n N = 100
  13. p := &n
  14.  
  15. n++
  16. f1 := n.test
  17.  
  18. n++
  19. f2 := p.test
  20.  
  21. n++
  22. fmt.Printf("main.n: %p, %v\n", p, n)
  23.  
  24. f1()
  25. f2()
  26.  
  27. }
  28.  
  29. /*
  30. main.n: 0xc00000a168, 103
  31. test.n: 0xc00000a1a0, 101
  32. test.n: 0xc00000a1b0, 102
  33. */

编译器会为method value生成一个包装函数,实现间接调用。
至于receiver复制,和闭包的实现方法基本一致,打包成funcval,经由DX寄存器传递。

当method value作为参数时,会复制给receiver在内的整个method value。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int
  6.  
  7. func call(m func()) {
  8. m()
  9. }
  10.  
  11. func (n N) test() {
  12. fmt.Printf("test.n: %p, %v\n", &n, n)
  13. }
  14. func main() {
  15. var n N = 100
  16. p := &n
  17.  
  18. fmt.Printf("main.n: %p, %v\n", p, n)
  19.  
  20. n++
  21. call(n.test)
  22.  
  23. n++
  24. call(p.test)
  25.  
  26. }
  27.  
  28. /*
  29. main.n: 0xc00004e080, 100
  30. test.n: 0xc00004e098, 101
  31. test.n: 0xc00004e0b8, 102
  32. */

如果目标方法的receiver是指针类型,那么被复制的仅是指针。

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. type N int
  6.  
  7. func (n *N) test() {
  8. fmt.Printf("test.n: %p, %v\n", n, *n)
  9. }
  10.  
  11. func main() {
  12. var n N = 100
  13. p := &n
  14.  
  15. n++
  16. f1 := n.test
  17.  
  18. n++
  19. f2 := p.test
  20.  
  21. n++
  22. fmt.Printf("main.n: %p, %v\n", p, n)
  23.  
  24. f1()
  25. f2()
  26. }
  27.  
  28. /*
  29. main.n: 0xc00004e080, 103
  30. test.n: 0xc00004e080, 103
  31. test.n: 0xc00004e080, 103
  32. */

  

go——方法的更多相关文章

  1. javaSE27天复习总结

    JAVA学习总结    2 第一天    2 1:计算机概述(了解)    2 (1)计算机    2 (2)计算机硬件    2 (3)计算机软件    2 (4)软件开发(理解)    2 (5) ...

  2. mapreduce多文件输出的两方法

    mapreduce多文件输出的两方法   package duogemap;   import java.io.IOException;   import org.apache.hadoop.conf ...

  3. 【.net 深呼吸】细说CodeDom(6):方法参数

    本文老周就给大伙伴们介绍一下方法参数代码的生成. 在开始之前,先补充一下上一篇烂文的内容.在上一篇文章中,老周检讨了 MemberAttributes 枚举的用法,老周此前误以为该枚举不能进行按位操作 ...

  4. IE6、7下html标签间存在空白符,导致渲染后占用多余空白位置的原因及解决方法

    直接上图:原因:该div包含的内容是靠后台进行print操作,输出的.如果没有输出任何内容,浏览器会默认给该空白区域添加空白符.在IE6.7下,浏览器解析渲染时,会认为空白符也是占位置的,默认其具有字 ...

  5. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  6. [C#] C# 基础回顾 - 匿名方法

    C# 基础回顾 - 匿名方法 目录 简介 匿名方法的参数使用范围 委托示例 简介 在 C# 2.0 之前的版本中,我们创建委托的唯一形式 -- 命名方法. 而 C# 2.0 -- 引进了匿名方法,在 ...

  7. ArcGIS 10.0紧凑型切片读写方法

    首先介绍一下ArcGIS10.0的缓存机制: 切片方案 切片方案包括缓存的比例级别.切片尺寸和切片原点.这些属性定义缓存边界的存在位置,在某些客户端中叠加缓存时匹配这些属性十分重要.图像格式和抗锯齿等 ...

  8. [BOT] 一种android中实现“圆角矩形”的方法

    内容简介 文章介绍ImageView(方法也可以应用到其它View)圆角矩形(包括圆形)的一种实现方式,四个角可以分别指定为圆角.思路是利用"Xfermode + Path"来进行 ...

  9. JS 判断数据类型的三种方法

    说到数据类型,我们先理一下JavaScript中常见的几种数据类型: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Functi ...

  10. .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法

    .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法 0x00 为什么需要Map(MapWhen)扩展 如果业务逻辑比较简单的话,一条主管道就够了,确实用不到 ...

随机推荐

  1. 内核补丁 patch

    https://www.kernel.org/diff/diffview.cgi?file=/pub/linux/kernel/v3.x/patch-3.18.12.xz

  2. UITextField小结

    //初始化textfield并设置位置及大小 UITextField *text = [[UITextField alloc]initWithFrame:CGRectMake(20, 20, 130, ...

  3. Python中如何将字符串作为变量名

    应用场景描述: 通过配置文件获取服务器上配置的服务名及运行端口号,编写python脚本检测服务上服务是否在运行? #!/usr/bin/env python # -*- coding:utf-8 -* ...

  4. 用Eclipse的tomcat插件启动tomcat时报错:

    用Eclipse的tomcat插件启动tomcat时报错: FATAL ERROR in native method: JDWP No transports initialized, jvmtiErr ...

  5. C#中oracle数据库的连接方法

    C#中oracle数据库的连接方法 一.关于数据库的操作 1.数据库连接      有2种:      第一种:古老的方法(较为死板,不利于灵活操作),即用OracleConnection的类来连接 ...

  6. jQuery DataTables添加自定义多个搜索条件

    效果如下: 一.在前台页面定义输入搜索条件的文本框 <div class="ibox-tools"> <span>年度</span> @Html ...

  7. 嵌入式驱动开发之解码器tvp5150---tvp5150am1基于8148vpss的添加调试

    (1)i2c (2)注册设备 (3)寄存器 --------------author:pkf ------------------------time:2015-4-5 --------------- ...

  8. (转)java Exception层次结构详解

    转自:http://www.importnew.com/14688.html 1. JAVA异常层次结构 异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程 ...

  9. 修改了JS代码,刷新网页后,加载的JS还是原来旧的?

    本地修改JS脚本后,刷新网页看一下修改后的执行效果,结果调试显示加载的JS还是原来旧的,反复刷新均无效,郁闷! 解决办法:清理一下浏览器缓存(长经验了!)     Ctrl+Shift+Del 清除G ...

  10. 0、手把手教React Native实战之开山篇

    ##作者简介 东方耀    Android开发   RN技术   facebook   github     android ios  原生开发   react reactjs nodejs 前端   ...