golang 方法

golang语言中的方法是与对象实例绑定的特殊函数,用于维护和展示对象的自身状态。

与函数的区别是方法有前置实例接收参数(receiver),编译器根据receiver来判断该方法属于哪个实例。receiver可以是基础类型,也可以是指针类型,这会关系到是否需要有可以修改对象实例的能力。

在调用方法时,可以使用对象实例值或指针,编译器会根据receiver类型自动在基础类型和指针类型之间转换,比如:

type rect struct {
    width, height, area int
}

func (r *rect) pointer() {
    r.width += 2
    r.area = r.width * r.height
}
func (r rect) value() {
    r.width += 4
    r.area = r.width * r.height
}

func main() {
    r := rect{width: 10, height: 5}
    r.value()
    fmt.Println(r)
    r.pointer()
    fmt.Println(r)
/*
    r := &rect{width: 10, height: 5}
    r.value()
    fmt.Println(r)
    r.pointer()
    fmt.Println(r)
*/ 
}
输出:
{10 5 0}
{12 5 60}

如果使用指针调用方法时,需要注意不能使用多级指针,且必须使用合法的指针(包括nil)或能取实例地址,比如:

type X struct{}

func (x *X)test(){
    fmt.Println("hello gopher")
}

func main(){
    var x *X
    fmt.Println(x)
    x.test()
    &X{}.test() //cannot take the address of X literal
}

如何选择方法的receiver类型?

- 如果要修改实例状态,用*T

- 如果不需要修改实例状态的小对象或固定值,建议用T

- 如果是大对象,建议用*T,可以减少复制成本

- 引用类型、字符串、函数等指针包装对象,用T

- 如果对象实例中包含Mutex等同步字段,用*T, 以免因复制造成锁无效

- 无法确定需求的情况,都用*T

通过匿名字段的方法访问:

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

type person  struct{}

type Man struct{
    person
}
func (p person)toWork()string{
    return "Tom"
}
func main(){
    var m Man 
    fmt.Println(m.toWork())
}
输出:
Tom

如果Man结构体也有个同名的toWork方法,此时调用逻辑如下,比如:

type person  struct{}

type Man struct{
    person
}

func (p person)toWork()string{
    return "Tom to work"
}

func (m Man)toWork()string {
    return "me to work"
}
func main(){
    var m Man
    fmt.Println(m.toWork())         //me to work
    fmt.Println(m.person.toWork())  //Tom to work
}

方法集:

GoLang规范中提到了一个与类型相关的方法集(method set),这决定了它是否实现了某个接口。

- 类型T方法集包含所有receiver T方法

- 类型*T方法集包含所有receiver T + *T的方法

- 匿名嵌入S,T方法集包含所有receiver S方法

- 匿名嵌入*S,T方法集包含所有receiver S + receiver *S方法

- 匿名嵌入*S或匿名嵌入S,*T方法集包含所有receiver S + receiver *S方法

type S struct{}

type T struct{
    S   
}

func (S) SVal()  {}  
func (*S) SPtr() {}
func (T) TVal()  {}  
func (*T) 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)
    println("--------------")
    methodSet(&t)
}
输出:
SVal func(main.T)
TVal func(main.T)
--------------
SPtr func(*main.T)
SVal func(*main.T)
TPtr func(*main.T)
TVal func(*main.T)

很显然, 匿名字段就是为扩展方法集准备的。否则没有必要少写个字段。这种组合没有父子依赖关系,

整体与局部松耦合,可以任意增加来实现扩展。各单元互无关联,实现与维护更加简单。

方法表达式:

方法是一种特殊的函数,除了可以直接调用之外,还可以进行赋值或当作参数传递,下面是Go语言的方法定义格式,比如:

func (p mytype) funcname(q type) (r,s type) { return 0,0}

本质上这就是一种语法糖,方法调用如下:

instance.method(args) -> (type).func(instance, args)

instance 就是Reciever,左边的称为Method Value;右边则是Method Expression,Go推荐使用左边形式。

Method Value是包装后的状态对象,总是与特定的对象实例关联在一起(类似闭包),

而Method Expression会被还原成普通的函数,将Receiver作为第一个显式参数,调用时需额外传递。

二者本质上没有区别,只是Method Value 看起来更像面向对象的格式,且编译器会自动进行类型转换;

Method Expression更直观,更底层,编译器不会进行类型转换,会按照实际表达意义去执行,更易于理解。

Method Expression:

type Person struct {
    Age int 
    Name string
}

func (p Person) GetAge() int{
    return  p.Age
}

func (p *Person) SetAge(i int){
    p.Age =  i
}

func main(){
    p := Person{20, "Tom"}
    setAge := (*Person).SetAge   //(\*Person)必须用括号,整体相当于func(p *Person)
    setAge(&p,50)                  //编译器不会进行类型转换
    getAge := Person.GetAge
    fmt.Println(getAge(p))
}
输出:
50

Method Value:

func main(){
    p := Person{20, "Tom"}
    setAge := p.SetAge      //编译器会自动进行类型转换
    setAge(50) 
    getAge := p.GetAge
    fmt.Println(getAge())
}

只要receiver参数类型正确,使用nil同样可以执行,比如:

type N int 

func (n N) value(){
    println(n)
}
func (n *N) pointer(){
    println(n)
}

func main(){
    var n *N
    n.pointer()
    (*N)(nil).pointer()
    (*N).pointer(nil)
}

这样写程序并没有什么意义,只是希望你能理解并安全使用Method Value和Method Expression

时间: 2024-10-25 17:07:32

golang 方法的相关文章

golang 方法内部定义子方法及调用

package main import ( "fmt" "reflect" ) func out(ch chan int) { <-ch fmt.Println(" test out func define") } func main() { //-----------------外部方法-------------------- ch := make(chan int) go out(ch) ch <- 1 //-----------

golang使用reflects调用方法时,方法名需要首字母大写

golang在服务端处理api请求,因为在其他语言中定义方法一般使用小写开头, 给服务端传递ApiName时一般使用的是小写首字母的方法名. 如果直接使用小写方法名定义方法,将无法通过golang的reflect反射获取和调用. 建议在增加前缀"API_"    如 API_login来定义Api结构的方法 type Api struct{ } func(this *Api)API_login(){ } requestStr := "usr/login/HYUKGDHJHDY

golang github.com/go-sql-driver/mysql 遇到的数据库,设置库设计不合理的解决方法

golang github.com/go-sql-driver/mysql 遇到的数据库,设置库设计不合理的解决方法,查询中报了以下这个错 Scan error on column index 2: unsupported Scan, storing driver.Value type <nil> 解决方案就是动态的把数据 字段前加一上一个COALESCE SELECT u.id,ta.`title` as `活动名` ,COALESCE(IFNULL(i.name,i.nickname) F

Golang使用pkg-config自动获取头文件和链接库的方法

为了能够重用已有的C语言库,我们在使用Golang开发项目或系统的时候难免会遇到Go和C语言混合编程,这时很多人都会选择使用cgo. 话说cgo这个东西可算得上是让人又爱又恨,好处在于它可以让你快速重用已有的C语言库,无需再用Golang重造一遍轮子,而坏处就在于它会在一定程度 上削弱你的系统性能.关于cgo的种种劣迹,Dave Cheney大神在他的博客上有一篇专门的文章<cgo is not Go>,感兴趣的同学可以看一看.但话说回来,有时候为了快速开发满足项目需求,使用cgo也实在是不得

Golang通过syscall调用windows dll方法

本用例在GO 1.4.2 上编译执行通过,直接上CODE: package main import (     "fmt"     "syscall"     "time"     "unsafe" ) const (     MB_OK                = 0x00000000     MB_OKCANCEL          = 0x00000001     MB_ABORTRETRYIGNORE  = 0x

【GoLang笔记】遍历map时的key随机化问题及解决方法

之前的一篇笔记曾分析过,Go的map在底层是用hashmap实现的.由于高效的hash函数肯定不是对key做顺序散列的,所以,与其它语言实现的hashmap类似,在使用Go语言map过程中,key-value的插入顺序与遍历map时key的访问顺序是不相同的.熟悉hashmap的同学对这个情况应该非常清楚. 所以,本文要提到的肯定不是这个,而是一个比较让人惊奇的情况,下面开始说明. 1. 通过range遍历map时,key的顺序被随机化 在golang 1.4版本中,借助关键字range对Go语

Golang 中操作 Mongo Update 的方法

Golang 和 MongoDB 中的 ISODate 时间交互问题 2018年02月27日 11:28:43 独一无二的小个性 阅读数:357 标签: GolangMongoDB时间交互时间转换 更多 个人分类: MongoDBGolang 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u010649766/article/details/79385948 MongoDB 中有一种时间格式数据 ISODate,参考如下:  如果在 Golan

Golang lint简易使用方法

根据作者的说法: Golint is a linter for Go source code. Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes. Golint differs from govet. Govet is concerned with correctness, whereas golint is concerned with codi

golang 函数和方法

由于自己是搞python开发的,所以在学习go时,当看到函数和方法时,顿时还是挺蒙的,因为在python中并没有明显的区别,但是在go中却是两个完全不同的东西.在官方的解释中,方法是包含了接收者的函数. 定义 函数的格式是固定的Func + 函数名 + 参数 + 返回值(可选) + 函数体 Func main( a, b int) (int) { } 而方法会在方法在func关键字后是接收者而不是函数名,接收者可以是自己定义的一个类型,这个类型可以是struct,interface,甚至我们可以