Go语言入门-方法

Go语言入门-方法

概念

A method is a function with a receiver. A method declaration binds an identifier, the method name, to a method, and associates the method with the receiver’s base type.

方法是一种特殊的函数,绑定一个receiver。 与接收者的基本类型关联。
基本语法

--官方描述
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver   = Parameters .
--通俗描述
func([Receiver] ReceiverType) methodName(ParamsList...)(ReturnList...) {
	FunctionBody 
	...
}

其中与函数定义的区别是在名字前面增加了(Receiver] Type)用来标识该方法的接受者的类型。并且 Reciver当在方法中不使用的时候可以不写

  • 示例1
type yourInt int
//定义一个方法,接收者的类型为yourInt,使用reciver来表示被绑定的yourInt类型的变量。
func (receiver yourInt) print() {
    println(receiver)
}
//定义一个方法,接收者的类型为yourInt,也就是只有yourInt类型才可以使用改方法,但是该方法中未使用receiver因此可以省略
func (yourInt) print2() {
    println("hello ")
}
func main() {
    your := yourInt(10)
    your.print()
    your.print2()
}
/**
output:
10
hello
*/

其中
1. Receiver-接受者的变量,关联被绑定的实例的标识符,不推荐使用this、self等,一般使用类型的首字母小写。当方法内部不引用被绑定的实例,则可以省略接收者变量的标识符。
2. ReceiverType-接收者类型,关联一个base类型T,而具体的类型可以是T或者T(此处T可以理解go中的引用)。T也不能是接口或指针类型。
3. 方法名-同函数
4. 参数列表-同函数
5. 返回值列表-同函数

使用

基本的使用(T)

示例2-一个点的类型point的基本使用

//定义一个点的类型point
type point struct {
    x, y int
}

//打印绑定变量本身
func (p point) printSelf() {
    fmt.Printf("[%p]->[%d, %d]\n", &p, p.x, p.y)
}

//打印传入的变量类型
func (point) print(p point) {
    fmt.Printf("[%p]->[%d, %d]\n", &p, p.x, p.y)
}
//打印坐标X
func (p point) printX() {
    fmt.Printf("[%p]->[%d, -]\n", &p, p.x)
}

//打印坐标Y
func (p point) printY() {
    fmt.Printf("[%p]->[-, %d]\n", &p, p.x)
}
//获取坐标X的值
func (p point) GetX() int {
    return p.x
}

//获取坐标Y的值
func (p point) GetY() int {
    return p.y
}

//坐标相加
func (p point) add(p2 point) {
    p.x += p2.x
    p.y += p2.y
}
//坐标相减
func (p point) sub(p2 point) {
    p.x -= p2.x
    p.y -= p2.y
}

func main() {
    sour := point{
        x: 1,
        y: 2,
    }
    other := point{
        x: 4,
        y: 3,
    }
    //打印自身
    sour.printSelf()
    //打印入参
    sour.print(other)
    //other变量自己打印
    other.printSelf()
    //打印X坐标
    sour.printX()
    //获取Y坐标值
    fmt.Println(sour.GetY())
    //两坐标相加
    sour.add(other)
    //打印相加的结果
    sour.printSelf()
    //两坐标相减
    sour.sub(other)
    //打印相减结果
    sour.printSelf()
}
/**
output:
[0xc00000a0f0]->[1, 2]
[0xc00000a130]->[4, 3]
[0xc00000a150]->[4, 3]
[0xc00000a170]->[1, -]
2
[0xc00000a190]->[1, 2]
[0xc00000a1b0]->[1, 2]
 */

以上示例中奇怪的是即使对 sour进行加减操作,但是sour的值始终不发生变化。以及每次打印的过程中地址都不一致。--------------------是什么原因呢?

  1. 方法的接收者的base Type为T,接收者类型是T则表示结构体变量做绑定的接收者是值传递,值传递不会修改接收者的内容,修改的是接收者的copy的变量。
  2. 方法的接收者的base Type为T,接收者类型是(*T) 表示使用的是绑定变量的地址,传递地址的时候也只值传递,但是地址所关联的内存确是接收者变量的内存,因此可以修改接收者的值。

以上用法为 值接收者用法。

指针接收者(*T)

  • 示例3 - 指针接收者
//定义一个点的类型point
type point struct {
    x, y int
}

//打印绑定变量本身
func (p *point) printSelf() {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}

//打印传入的变量类型
func (*point) print(p *point) {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}
//打印坐标X
func (p *point) printX() {
    fmt.Printf("[%p]->[%d, -]\n", p, p.x)
}

//打印坐标Y
func (p *point) printY() {
    fmt.Printf("[%p]->[-, %d]\n", p, p.x)
}
//获取坐标X的值
func (p *point) GetX() int {
    return p.x
}

//获取坐标Y的值
func (p *point) GetY() int {
    return p.y
}

//坐标相加
func (p *point) add(p2 point) {
    p.x += p2.x
    p.y += p2.y
}
//坐标相减
func (p *point) sub(p2 *point) {
    p.x -= p2.x
    p.y -= p2.y
}

func main() {
    sour := point{
        x: 1,
        y: 2,
    }
    other := point{
        x: 4,
        y: 3,
    }
    //打印自身
    sour.printSelf()
    //打印入参
    sour.print(&other)
    //other变量自己打印
    other.printSelf()
    //打印X坐标
    sour.printX()
    //获取Y坐标值
    fmt.Println(sour.GetY())
    //两坐标相加
    sour.add(other)
    //打印相加的结果
    sour.printSelf()
    //两坐标相减-相减两次
    sour.sub(&other)
    sour.sub(&other)
    //打印相减结果
    sour.printSelf()
}
/**
output:
[0xc00000a0f0]->[1, 2]
[0xc00000a100]->[4, 3]
[0xc00000a100]->[4, 3]
[0xc00000a0f0]->[1, -]
2
[0xc00000a0f0]->[5, 5]
[0xc00000a0f0]->[-3, -1]
*/

以上通过使用指针接收者实现的方法中,可以看到sour的地址为[0xc00000a0f0]other的地址为[0xc00000a100] 使用指针接收者以后地址不再发生变化。

如何选择接收者类型

  1. 如果要修改实例,用*T
  2. 不修改实例,且实例尺寸小用T,也可以用*T
  3. 大尺寸示例用*T
  4. 包含 同步字段Mutex等,使用*T
  5. 引用类型、字符串、函数等指针包装对对象,建议用T
  6. 其他使用 *T
    以上来自《Go语言学习笔记》

语法糖:实例或者是实例地址调用方法

可以使用 【实例+“.”+方法】进行方法调用,也可以使用【实例地址+“.”+方法】

  • 示例4-实例地址调用T方法
//定义一个点的类型point
type point struct {
    x, y int
}
//打印绑定变量本身
func (p point) printSelf() {
    fmt.Printf("[%p]->[%d, %d]\n", &p, p.x, p.y)
}

func main() {
    sour := point{
        x: 1,
        y: 2,
    }
    ptr := &sour
    fmt.Printf("ptr = [%p]\n", ptr)
    ptr.printSelf()
    ptr.printSelf()
    sour.printSelf()
    sour.printSelf()
}
/**
ptr = [0xc000072090]
[0xc0000720c0]->[1, 2]
[0xc0000720e0]->[1, 2]
[0xc000072100]->[1, 2]
[0xc000072120]->[1, 2]
 */

通过以上例子中:可以看出receiver是value-值类型, 但是可以传入实例的地址进行调用,ptr是实例sour的地址。之前也提到过这种方式是一种语法糖,实际上调用过程会转换为(*ptr).printSelf()。那是不是调用是通过ptr传入的地址,意味着传递进去的是实例sour的地址吗?答案是否定的,两次调用ptr.printSelf()打印出的地址分别是[0xc0000720c0][0xc0000720e0]而ptr的值是[0xc000072090]。那么可以得出以下结论:

** 实例的地址或者实例都可以调用方法。通过指针调用实际是一个语法糖,对于值接收者方法编译器会转换指针ptr.method为**(*ptr).method.

  • 示例5-实例地址调用*T方法

func main() {
    sour := point{
        x: 1,
        y: 2,
    }
    ptr := &sour
    fmt.Printf("ptr = [%p]\n", ptr)
    //不管是实例地址还是实例去调用 地址均为发生变化。
    ptr.printSelf2()
    ptr.printSelf2()
    sour.printSelf2()
    sour.printSelf2()
    //发生值值传递地址改变
    ptr.printSelf()
}
/**
ptr = [0xc00000a0f0]
[0xc00000a0f0]->[1, 2]
[0xc00000a0f0]->[1, 2]
[0xc00000a0f0]->[1, 2]
[0xc00000a0f0]->[1, 2]
[0xc00000a150]->[1, 2]
 */

通过以上例子中:可以看出receiver是-poinit类型, 但是可以传入实例进行调用如sour.printSelf2()。之前也提到过这种方式是一种语法糖,实际上调用过程会转换为(&sour).printSelf2()。那是不是调用是通过sour传入实例,意味着传递进去的是实例sour的副本吗?答案是否定的,两次调用ptr.printSelf2()打印出的地址分别是[0xc00000a0f0][0xc00000a0f0]而ptr的值也是[0xc00000a0f0]。那么可以得出以下结论:

实例的地址或者实例都可以调用方法。通过指针调用实际是一个语法糖,对于指针接收者防范编译器会转换指针sour.method为(&sour).method.

整理总结:

1. 实例的地址或者实例都可以调用方法。通过指针调用实际是一个语法糖,对于值接收者方法编译器会转换指针ptr.method为(*ptr).method.
2. 实例的地址或者实例都可以调用方法。通过指针调用实际是一个语法糖,对于指针接收者防范编译器会转换指针sour.method为(&sour).method.
3. 方法对实例使用值传递还是引用传递取决于方法的定义,而不是通过实例和实例地址调用方法的方式区别。

匿名字段的方法

之前在结构体中可以访问匿名字段的成员,在go语言中也可以使用匿名字段的方法。

  • 示例6-结构体基本的组合
//定义一个点的类型point
type point struct {
    x, y int
}

//打印绑定变量本身
func (p *point) printSelf() {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}
//打印坐标X
func (p *point) printX() {
    fmt.Printf("[%p]->[%d, -]\n", p, p.x)
}

//打印传入的变量类型
func (*point) print(p *point) {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}

type line struct {
    start point
    end point
}
func main() {
    l := line{
    start: point{0, 0},
    end:   point{1, 1},
    }
    fmt.Println(l)
    //调用组合的类型方法
    l.start.printSelf()
    //end的print方法传入l.start地址当做入参
    l.end.print(&l.start)
    l.end.printSelf()
}
/**
output:
{{0 0} {1 1}}
[0xc000074140]->[0, 0]
[0xc000074140]->[0, 0]
[0xc000074150]->[1, 1]
 */

以上例子中该有 start和end两个成员变量,可以使用【实例变量名】+ “.” +【成员变量名】+"." + 【方法】这种方式访问成员变量方法。

  • 示例7-结构体 匿名字段
    我们可以改造以上的例子可以省略调用 start的成员变量名称。
//定义一个点的类型point
type point struct {
    x, y int
}

//打印绑定变量本身
func (p *point) printSelf() {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}
//打印坐标X
func (p *point) printX() {
    fmt.Printf("[%p]->[%d, -]\n", p, p.x)
}

//打印传入的变量类型
func (*point) print(p *point) {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}

type line struct {
    start point
    end point
}
type line2 struct {
    point
    end point
}

func main() {
    l := line2{
        point: point{0, 0},
        end:   point{2, 2},
    }
    fmt.Println(l)
    //通过匿名变量类型值访问匿名变量方法
    l.point.printSelf()
    //可以直接使用匿名变量的方法
    l.printSelf()
    //命名变量显示调用
    l.end.printSelf()
}
/**
output:
{{0 0} {2 2}}
[0xc000070140]->[0, 0]
[0xc000070140]->[0, 0]
[0xc000070150]->[2, 2]
 */

以上例子中 存在匿名变量point,可以使用【实例变量名】+ “.” +【匿名变量类型】+"." + 【方法】这种方式访问成员变量方法也可以直接通过【实例变量名】+"." + 【方法】进行调用。

  • 示例8-当匿名成员的方法不满足已经组合的结构体时,通过结构体同名遮蔽的特性,实现覆盖(override)的逻辑
//定义一个点的类型point
type point struct {
    x, y int
}

//打印绑定变量本身
func (p *point) printSelf() {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}
//打印坐标X
func (p *point) printX() {
    fmt.Printf("[%p]->[%d, -]\n", p, p.x)
}

//打印传入的变量类型
func (*point) print(p *point) {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}

type line struct {
    start point
    end point
}
type line2 struct {
    point
    end point
}
type line3 struct {
    point
    end point
}
func (l *line3) printSelf()  {
    fmt.Printf("[%p]->[start:[%d, %d],end:[%d, %d]]\n", l, l.x, l.y, l.end.x, l.end.y)
}
func main() {
    l := line3{
        point: point{0, 0},
        end:   point{2, 2},
    }
    fmt.Println(l)
    //通过同名遮盖,就访问不到匿名变量的方法,而调用新增加的方法printSelf
    l.printSelf()
    
    //通过匿名变量类型值访问匿名变量方法
    l.point.printSelf()
    //命名变量显示调用
    l.end.printSelf()
}
/**
{{0 0} {2 2}}
[0xc000012320]->[start:[0, 0],end:[2, 2]]
[0xc000012320]->[0, 0]
[0xc000012330]->[2, 2]
 */

通过结构体同名遮蔽的特性,可以实现覆盖(override)的逻辑

方法集

什么是方法集

A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.

一句话说明就是:每一个类型都关联一个对应的方法集,接口的方法集是接口本身。其中:
类型T的方法集包含了所有receiverT方法。
类型 *T 方法集包含所有receiver T + *T 方法。

获取方法集

  • 示例9-通过反射获取方法集
//定义一个点的类型point
type pointE struct {
    x, y int
}
//打印绑定变量本身
func (p pointE) printSelf() {
    fmt.Printf("[%p]->[%d, %d]\n", &p, p.x, p.y)
}
func (p *pointE) printSelf2() {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}
//打印传入的变量类型
func (pointE) print(p pointE) {
    fmt.Printf("[%p]->[%d, %d]\n", &p, p.x, p.y)
}
//打印坐标X
func (p pointE) printX() {
    fmt.Printf("[%p]->[%d, -]\n", &p, p.x)
}
//打印坐标X
func (p *pointE) printX2() {
    fmt.Printf("[%p]->[%d, -]\n", &p, p.x)
}

func GetMethodSet(a interface{})  {
    tp := reflect.TypeOf(a)
    n := tp.NumMethod()
    fmt.Println(n)
    for i := 0 ; i < n; i++ {
        m := tp.Method(i)
        fmt.Println(m.Name, m.Type)
    }
}
func main() {
    var p pointE
    GetMethodSet(p)
}
/**
output:
0
 */

奇怪为什么没有打印出来方法集呢 ?

重点:通过NumMethod和Method只能拿到公有方法也就是方法名是大写的。

  • 示例10-通过反射获取方法集-改造版本
//定义一个点的类型point
type pointEE struct {
    x, y int
}
//打印绑定变量本身
func (p pointEE) PrintSelf() {
    fmt.Printf("[%p]->[%d, %d]\n", &p, p.x, p.y)
}
func (p *pointEE) PrintSelf2() {
    fmt.Printf("[%p]->[%d, %d]\n", p, p.x, p.y)
}
//打印传入的变量类型
func (pointEE) Print(p pointEE) {
    fmt.Printf("[%p]->[%d, %d]\n", &p, p.x, p.y)
}
//打印坐标X
func (p pointEE) PrintX() {
    fmt.Printf("[%p]->[%d, -]\n", &p, p.x)
}
//打印坐标X
func (p *pointEE) PrintX2() {
    fmt.Printf("[%p]->[%d, -]\n", &p, p.x)
}

func GetMethodSet(a interface{})  {
    tp := reflect.TypeOf(a)
    n := tp.NumMethod()
    fmt.Println(n)
    for i := 0 ; i < n; i++ {
        m := tp.Method(i)
        fmt.Println(m.Name, m.Type)
    }
}
func main() {
    var p pointEE
    GetMethodSet(p)
    fmt.Println("******************")
    var ptr = &p
    GetMethodSet(ptr)
}

/**
output:
3
Print func(main.pointEE, main.pointEE)
PrintSelf func(main.pointEE)
PrintX func(main.pointEE)
******************
5
Print func(*main.pointEE, main.pointEE)
PrintSelf func(*main.pointEE)
PrintSelf2 func(*main.pointEE)
PrintX func(*main.pointEE)
PrintX2 func(*main.pointEE)
 */

可以看出把函数名调整为大写就能通过反射出来具体的函数集合,通过以上结果我们可以得出来:

类型T的方法集包含了所有receiverT方法。
类型 *T 方法集包含所有receiver T + *T 方法。
通过反射只能拿到对应的公用方法


方法集作用是什么?

方法集的作用指针只作用于接口的实现和表达式的转换,从上面的例子我们可以得出通过实例或者实例指针可以获得不同的房集合。说通俗一点,当使用实例指针访问方法时能够使用很多的方法。当该实例被组合到结构体中也是同样的情况,大那是接口提中存在匿名字段。这时候方法集看上去具备的继承的特性。
来个例子

  • 示例11-实现匿名字段的方法集“继承”。
//定义类型lineEE 两个点一个长度
type lineEE struct {
    pointEE
    end pointEE
    len int
    
}
//定义一个pointEX 是对pointEE的封装
type pointEX struct {
    *pointEE
    name string
}

//增加长度赋值的方法
func (l *lineEE) SetLen(len1 int)  {
    l.len = len1
}
//增加获取长度值的方法
func (l lineEE) GetLen() int {
    return l.len
}
func GetMethodSet(a interface{})  {
    tp := reflect.TypeOf(a)
    n := tp.NumMethod()
    fmt.Println(n)
    for i := 0 ; i < n; i++ {
        m := tp.Method(i)
        fmt.Println(m.Name, m.Type)
    }
}

func main() {
    var p pointEE
    //打印遍历pointEE值方法
    GetMethodSet(p)
    fmt.Println("******************")
    var ptr = &p
    //打印遍历pointEE指针和值方法
    GetMethodSet(ptr)
    fmt.Println("******************")
    var l lineEE
    // 打印遍历lineEE值方法 含:line的值方法。匿名字段pointEE的值方法
    GetMethodSet(l)
    fmt.Println("******************")
    // 打印遍历lineEE指针方法 含:line的指针方法。匿名字段pointEE的值方法 + 匿名字段的指针方法
    GetMethodSet(&l)
    fmt.Println("******************")
    var pex pointEX
    //打印遍历pointEx的方法值,因为是匿名字段pointEE指针,因此包含了值方法和指针方法。
    GetMethodSet(pex)
}
/**
output:
3
Print func(main.pointEE, main.pointEE)
PrintSelf func(main.pointEE)
PrintX func(main.pointEE)
******************
5
Print func(*main.pointEE, main.pointEE)
PrintSelf func(*main.pointEE)
PrintSelf2 func(*main.pointEE)
PrintX func(*main.pointEE)
PrintX2 func(*main.pointEE)
******************
4
GetLen func(main.lineEE) int
Print func(main.lineEE, main.pointEE)
PrintSelf func(main.lineEE)
PrintX func(main.lineEE)
******************
7
GetLen func(*main.lineEE) int
Print func(*main.lineEE, main.pointEE)
PrintSelf func(*main.lineEE)
PrintSelf2 func(*main.lineEE)
PrintX func(*main.lineEE)
PrintX2 func(*main.lineEE)
SetLen func(*main.lineEE, int)
******************
5
Print func(main.pointEX, main.pointEE)
PrintSelf func(main.pointEX)
PrintSelf2 func(main.pointEX)
PrintX func(main.pointEX)
PrintX2 func(main.pointEX)
*/

从以上例子中我们也验证了方法集的匿名嵌套的几个特性。

匿名嵌套S,T方法集包含全部的值方法(receiver S)
匿名嵌套*S,T方法集包含全部的值方法(receiver S)+ 指针方法(receiver *S)
匿名嵌套S或者*S,*T方法集包含所有的(receiver S)+ 指针方法(receiver *S)

方法集"继承" 和使用的特性

类型T的方法集包含了所有receiverT方法。
类型 *T 方法集包含所有receiver T + *T 方法。
匿名嵌套S,T方法集包含全部的值方法(receiver S)
匿名嵌套*S,T方法集包含全部的值方法(receiver S)+ 指针方法(receiver *S)
匿名嵌套S或者*S,*T方法集包含所有的(receiver S)+ 指针方法(receiver *S)

方法表达式

方法和函数一样,不仅支持直接调用,还可以赋值给变量或作为函数传递。存在值传递或者引用传递不同的方式。Go语言分为两种。分别是method expression和method value。

基本的语法

//method value
instance.Method(args....)
// method expression
type.func(instance, args....)


method value

  • 示例12-method value演示
type point struct {
    x, y int
}

func (p *point) Set(x, y int) {
    p.x = x
    p.y = y
    fmt.Printf("Addr=[%p] Set:x=[%d] y=[%d]\n", p, p.x, p.y)
}
func (p point) Get() (x, y int) {
    fmt.Printf("Addr=[%p] Get:x=[%d] y=[%d]\n", &p, p.x, p.y)
    return p.x, p.y
}
func main() {
    p := point{
        x: 0,
        y: 0,
    }
    p.Set(1,1)
    p.Get()
    //method value
    fmt.Println("method value ")
    fValueSet := p.Set
    fValueGet := p.Get
    fValueSet(3, 3)
    fValueGet()
}
/**
output:
Addr=[0xc00000a0f0] Set:x=[1] y=[1]
Addr=[0xc00000a120] Get:x=[1] y=[1]
method value
Addr=[0xc00000a0f0] Set:x=[3] y=[3]
Addr=[0xc00000a150] Get:x=[1] y=[1]
 */

method value隐式绑定了实参。

  1. 当方法是值方法时,通过变量保存方法时,实际上会重新开辟一块内存存放赋值时已经绑定变量的副本。
  2. 当方法是指针方法时,通过变量保存方法是,只会开辟一块内存存放该实例的地址。从而实现看上去是引用的方式。
  • 示例13-method value经典示例
type N int

func (n N) test() {
    fmt.Printf("test.n -> addr=[%p], %v\n", &n, n)
}

func (n *N) testP() {
    fmt.Printf("test.n -> addr=[%p], %d\n", n, *n)
}
func main() {
    n := N(1)
    p := &n
    n++
    f1 := p.test //f1绑定的是当前p所指向的副本,当前p的值为2
    fp1 := n.testP //fp1绑定的是当前的n 指针方法
    n++
    f2 := n.test //f2绑定的是当前n的副本,当前n的值为3
    fp2 := p.testP //fp2绑定的是当前的p所指向的n 指针方法
    n++
    f3 := p.test //f3绑定的是当前p所指向的副本,当前n的值为4
    fp3 := n.testP //fp3绑定的是当前的n 指针方法 
    fmt.Printf("main.n -> addr=[%p], %v\n\n", &n, n)
    //值方法绑定后调用,值方法会生成赋值时状态下的n的副本
    f1()
    f2()
    f3()
    //指针方法所关联的地址是n也就是p所指向的内存,
    //由于该内存的值以及改变了,所以指针方法调用打印的是地址的当前值
    fp1()
    fp2()
    fp3()
}
/**
output:
main.n -> addr=[0xc00000a0d8], 4
test.n -> addr=[0xc00000a0f8], 2
test.n -> addr=[0xc00000a128], 3
test.n -> addr=[0xc00000a138], 4
test.n -> addr=[0xc00000a0d8], 4
test.n -> addr=[0xc00000a0d8], 4
test.n -> addr=[0xc00000a0d8], 4
 */

method expresion

通过method expression,方法会被还原为普通函数的,receiver就会作为第一个个参数。在调用的时候需要显示的传参数。而类型可以是 T或者T。在方法集的介绍中T 和T包含的方法集是不一样的。这点需要注意。

type N int

func (n N) test() {
    fmt.Printf("test.n -> addr=[%p], %v\n", &n, n)
}

func (n *N) testP() {
    fmt.Printf("test.n -> addr=[%p], %d\n", n, *n)
}
func main() {
    n := N(1)
    fmt.Printf("main.n -> addr=[%p], %v\n\n", &n, n)
    n++
    f1 := N.test //f1是还原的普通函数func(main.N)  参数类型是N
    //invalid method expression N.testP (needs pointer receiver: (*N).testP)
    //fp1 := N.testP //fp1是还原的普通函数func(* main.N)  参数类型是 *N
    fp1 := (*N).testP //fp1是还原的普通函数func(* main.N)  参数类型是 *N
    fmt.Printf("%T\n", f1)
    fmt.Printf("%T\n", fp1)
    //expression不同绑定实例值
    f1(n)
    fp1(&n)
}
/**
output:
main.n -> addr=[0xc00000a0d8], 1

func(main.N)
func(*main.N)
test.n -> addr=[0xc00000a0f8], 2 ---和 addr=[0xc00000a0d8]不一致
test.n -> addr=[0xc00000a0d8], 2
 */

method expression就是把方法集进行还原为函数,receiver是T入参就是T, receiver是T入参就是T,编译器也不会进行转换。

你可能感兴趣的:(GO语言,golang,method,方法,go,教程)