• go——方法


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

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

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

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

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

    package main
    
    import "fmt"
    
    type N int
    
    func (N) test() {
    	fmt.Println("hello, world")
    }
    
    func main() {
    	var n N = 3
    	n.test()
    }
    

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

    package main
    
    import "fmt"
    
    type N int
    
    func (n N) value() {
    	n++
    	fmt.Printf("v: %p, %v
    ", &n, n)
    }
    
    func (n *N) pointer() { //指针类型
    	(*n)++ //通过指针反取得到数据
    	fmt.Printf("p: %p, %v
    ", n, *n)
    }
    
    func main() {
    	var a N = 10
    
    	a.value()   //值传递,复制
    	a.pointer() //指针引用传递,共用
    
    	fmt.Printf("a: %p, %v
    ", &a, a)
    }
    
    /*
    v: 0xc00004e088, 11
    p: 0xc00004e080, 11  //通过指针传值,其实是指向原数据
    a: 0xc00004e080, 11
    */
    

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

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

    package main
    
    import "fmt"
    
    type N int
    
    func (n N) value() {
    	n++
    	fmt.Printf("v: %p, %v
    ", &n, n)
    }
    
    func (n *N) pointer() {
    	(*n)++
    	fmt.Printf("p: %p, %v
    ", n, *n)
    }
    
    func main() {
    	var a N = 10
    	p := &a
    
    	fmt.Printf("a: %p, %v
    ", &a, a)
    	a.value()
    	a.pointer() //这里虽然传递的是整数类型,但是编译器会自动判断
    
    	p.value()
    	p.pointer()
    	fmt.Printf("a: %p, %v
    ", &a, a)
    }
    
    /*
    a: 0xc00004e080, 10
    
    v: 0xc00004e098, 11  //复制值,+1,并没有改变原数据,变量指向了另一个内存地址
    p: 0xc00004e080, 11  //指针传递,+1,改变了元数据
    
    v: 0xc00004e0d0, 12  //11 + 1
    p: 0xc00004e080, 12  //11 + 1
    
    a: 0xc00004e080, 12  //使用指针传递的时候,值改变并不会改变变量的内存地址
    */
    

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

    package main
    
    import "fmt"
    
    type N int
    
    func (n N) value() {
    	n++
    	fmt.Printf("v: %p, %v
    ", &n, n)
    }
    
    func (n *N) pointer() {
    	(*n)++
    	fmt.Printf("p: %p, %v
    ", n, *n)
    }
    
    func main() {
    	var a N = 25
    
    	p := &a
    	p2 := &p
    
    	p2.value() //calling method value with receiver p2 (type **N) requires explicit dereference
    	p2.pointer()
    }
    

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

    package main
    
    import "fmt"
    
    type X struct{}
    
    func (x, *X) test() {
    	fmt.Println("hi!")
    }
    
    func main() {
    	var a *X
    	X{}.test() //结果体没有指针属性
    
    }
    

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

    package main
    
    import (
    	"sync"
    )
    
    type data struct {
    	sync.Mutex
    	buf [1024]byte
    }
    
    func main() {
    	d := data{}
    	d.Lock() //直接调用结构体字段的方法
    	defer d.Unlock()
    }
    

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

    package main
    
    import "fmt"
    
    type user struct{}
    
    type manager struct {
    	user
    }
    
    func (user) toString() string {
    	return "user"
    }
    
    func (m manager) toString() string {
    	return m.user.toString() + ",manager"
    }
    
    func main() {
    	var m manager
    	fmt.Println(m.toString())      //user,manager
    	fmt.Println(m.user.toString()) //user
    }
    

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

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

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type S struct{}
    
    type T struct {
    	S
    }
    
    func (S) sVal()  {}
    func (*S) sPtr() {}
    func (T) tVal()  {}
    func (*S) tPtr() {}
    
    func methodSet(a interface{}) {
    	t := reflect.TypeOf(a)
    
    	for i, n := 0, t.NumMethod(); i < n; i++ {
    		m := t.Method(i)
    		fmt.Println(m.Name, m.Type)
    	}
    }
    
    func main() {
    	var t T
    	methodSet(t)
    	fmt.Println("-------------")
    	methodSet(&t)
    }
    

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

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

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


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

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

    package main
    
    import "fmt"
    
    type N int
    
    func (n N) test() {
    	fmt.Printf("test.n: %p, %d
    ", &n, n)
    }
    
    func main() {
    	var n N = 25
    	fmt.Printf("main.n: %p, %d
    ", &n, n)
    
    	f1 := N.test //直接将方法赋值给变量
    	f1(n)
    
    	f2 := (*N).test
    	f2(&n)
    
    	N.test(n) //直接以表达式方式调用
    	(*N).test(&n)
    }
    
    /*
    main.n: 0xc00004e080, 25
    test.n: 0xc00004e098, 25
    test.n: 0xc00004e0b8, 25
    */
    

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

    package main
    
    import "fmt"
    
    type N int
    
    func (n N) test() {
    	fmt.Printf("test.n: %p, %v
    ", &n, n)
    }
    
    func main() {
    	var n N = 100
    	p := &n
    
    	n++
    	f1 := n.test
    
    	n++
    	f2 := p.test
    
    	n++
    	fmt.Printf("main.n: %p, %v
    ", p, n)
    
    	f1()
    	f2()
    
    }
    
    /*
    main.n: 0xc00000a168, 103
    test.n: 0xc00000a1a0, 101
    test.n: 0xc00000a1b0, 102
    */
    

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

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

    package main
    
    import "fmt"
    
    type N int
    
    func call(m func()) {
    	m()
    }
    
    func (n N) test() {
    	fmt.Printf("test.n: %p, %v
    ", &n, n)
    }
    func main() {
    	var n N = 100
    	p := &n
    
    	fmt.Printf("main.n: %p, %v
    ", p, n)
    
    	n++
    	call(n.test)
    
    	n++
    	call(p.test)
    
    }
    
    /*
    main.n: 0xc00004e080, 100
    test.n: 0xc00004e098, 101
    test.n: 0xc00004e0b8, 102
    */
    

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

    package main
    
    import "fmt"
    
    type N int
    
    func (n *N) test() {
    	fmt.Printf("test.n: %p, %v
    ", n, *n)
    }
    
    func main() {
    	var n N = 100
    	p := &n
    
    	n++
    	f1 := n.test
    
    	n++
    	f2 := p.test
    
    	n++
    	fmt.Printf("main.n: %p, %v
    ", p, n)
    
    	f1()
    	f2()
    }
    
    /*
    main.n: 0xc00004e080, 103
    test.n: 0xc00004e080, 103
    test.n: 0xc00004e080, 103
    */
    

      

  • 相关阅读:
    jquery中attr和prop的区别
    director.js:客户端的路由---简明中文教程
    MVC利用Routing实现多域名绑定一个站点、二级域名以及二级域名注册Area
    机器学习——几种分类算法的汇总
    图像预处理(二值化)
    深度学习训练数据打标签过程
    卷积神经网络(CNN)
    机器学习算法中的准确率(Precision)、召回率(Recall)、F值(F-Measure)
    TortoiseGit上传项目到GitHub
    机器学习中的数据清洗与特征工程
  • 原文地址:https://www.cnblogs.com/yangmingxianshen/p/10090015.html
Copyright © 2020-2023  润新知