• 你好!欢迎你的到来
  • 关于我们
  • 首页 博客 学习笔记 技术导航 工具
  • 博文分类
    • PHP(43)
    • MySQL(11)
    • Linux(28)
    • html(3)
    • JQuery(4)
    • JavaScript(9)
    • svn(2)
    • CSS(2)
    • seajs(1)
    • go(44)
    • redis(1)
    • nginx(8)
    • mongo(0)
    • es(0)
    • 算法(0)
    • 其他(26)
    • 生活(1)
    专栏
    • Jquery基础教程
      • 文章:(15)篇
      • 阅读:17129
    • shell命令
      • 文章:(42)篇
      • 阅读:58556
    • Git教程
      • 文章:(36)篇
      • 阅读:115214
    • leetCode刷题
      • 文章:(37)篇
      • 阅读:14590
    • 摘要视图
    • 目录视图
    go的方法集详解
    2019-02-25 22:28 阅读(2605) 评论(0)

    一、什么是方法集

    在go语言中,每个类型都有与之关联的方法,把这个类型的所有方法称为类型的方法集。如下:

    type Student struct {
       age int8
       name string
    } 
    
    func (s Student) showName() {
       fmt.Println(s.name)
    }
    
    func (s * Student) setName(newName string) {
       s.name = newName
    }

    类型Student方法集包含了showName()方法。

    类型*Student方法集包含了showName()方法和setName()方法。

    为什么呢?因为:

    1、类型 T 方法集,包含全部 receiver T 方法。

    2、类型 *T 方法集,包含全部 receiver T + *T 方法。

    二、方法集和方法接受者的关系

    在上面的案例中,类型Student的方法法集并不包含了setName()方法,那么是不是Student类型变量,就不能调用setName()方法呢?即下面调用,是否会报错呢?

    s := Student{}
    s.setName("dq")

    其实,上面的调用是ok的。为什么呢?我们来回顾一下go语言的方法定义。

    1、参数 receiver 可任意命名,如方法中,不使用参数,可以省略参数名。

    2、参数 receiver 类型可以是 T 或 *T,但类型T不能为接口或指针类型。

    3、不支持方法重载。

    4、可以实例value或pointer调用全部的方法,编译器会自动转换。

    如下:

    type Student struct {
       age int8
       name string
    }
    type StudentPoint *Student
    
    func (Student) sayHello() {  //省略receiver 的参数参数名字
       fmt.Println("hello world")
    }
    
    func (s Student) showName() { 
       fmt.Println(s.name)
    }
    
    func (s * Student) setName(newName string) {
       s.name = newName
    }
    
    func (s StudentPoint) showName2(){ // Error:接受者(receiver)为指针类型
       fmt.Println(s.name)
    }
    
    s := Student{}
    s.setName("dq") //go会自动转为 (&s).setName("dq")
    
    var s2 = &s
    s2.showName() //o会自动转为 (*s2).showName()

    所以,当类型调用自己申明的方法的时候,不需要考虑方法集。方法接受者是值类型(T),还是指针类型(*T),影响T类型的实体变量的方法集。

    三、方法集和接口

    1、接口的定义

    接口是一个或多个方法签名的集合。任何类型的方法集中只要拥有该接口“对应的全部方法”签名。就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。

    对应的全部方法:是指有相同名称、参数列表 (不包括参数名) 以及返回值。

    接口只有方法的申明,没有实现。

    接口可以匿名嵌入到其他接口,或是嵌入结构体中。

    接口命名习惯以 er 结尾。

    type Personer interface { //定义一个接口
       showName()
    }
    
    type Student struct {
       Personer //嵌入接口
    }

    2、接口执行机制

    接口对象,是有接口表(interface table)和数据指针组成。

    接口表存储元数据信息,包括接口类型、动态类型,以及实现接口的方法指针。

    数据指针持有的是目标对象的只读复制品,复制完整对象或指针。

    package main
    
    import (
       "fmt"
       "reflect"
    )
    
    type User struct {
       id   int
       name string
    }
    
    type Student struct {
       id   int
       name string
    }
    
    func main() {
       u := User{1, "Tom"}
       var i interface{} = u // 由于interface{}不包含任何方法,所以任何类型,都实现了interface{}接口
       fmt.Println(reflect.TypeOf(i)) //main.User
    
       i = Student{}
       fmt.Println(reflect.TypeOf(i)) //main.Student
    }

    3、以实体类型实现接口和以指针类型实现接口的区别

    1、若以实体类型(T)实现接口,不管是T类型的值,还是T类型的指针,都实现了该接口。

    2、若以指针类型(*T)实现接口,只有T类型的指针,才实现了该接口。

    type Animal interface { //接口
       say()
    }
    
    type Dog struct {
       name string
    }
    
    type Cat struct {
       name string
    }
    
    func (_self Dog) say() {
       fmt.Println("I am", _self.name)
    }
    
    func (_self *Cat) say() {
       fmt.Println("I am", _self.name)
    }
    
    func main() {
       var d1 Animal= Dog{name:"wangCai1"}
       d1.say() //因为Dog 的value类型实现了Animal接口
    
       var d2 Animal= &Dog{name:"wangCai2"}
       d2.say() //因为dDog 的指针类型实现了Animal接口
    
       var c1 Animal= Cat{name:"miaoMiao1"}
       c1.say() //因为Cat 的value类型没有实现了Animal接口,所以报错
    
    
       var c2 Animal= &Dog{name:"miaoMiao2"}
       c2.say() //因为Cat 的指针类型实现了Animal接口
    }

    类型必须实现接口的所有方法,才能表示它 "实现" 了该接口,如下:

    type Animal interface {
       say()
       doSome()
    }
    
    type Dog struct {
       name string
    }
    func (_self Dog) say() {
       fmt.Println("I am", _self.name)
    }
    func (_self *Dog) doSome() {
       fmt.Println("I will do something")
    }
    
    func main() {
       // 报错,因为Dog的value类型实现了Animal接口的say方法没有实现doSome方法
       var d1 Animal= Dog{name:"wangCai1"} 
       d1.say()
    
       //因为dDog 的指针类型实现了Animal接口集的所有方法
       var d2 Animal= &Dog{name:"wangCai2"}
       d2.say() 
    }

    四、方法集和嵌入

    1、什么是嵌入

    go语言中,所谓的嵌入,即把一个类型作为另外一个类型的匿名字段。如下:

    type Person struct {
       age  int8
       name string
    }
    
    type Student struct {
       Person //嵌人 Persion类型
    }

    go语言通过嵌入组合,来实现继承的行为。于是,我们就可以通过Student类型的实例,访问Persion类型的变量和方法。如下:

    var s = Student{}
    s.name = "dq"


    2、值类型(T)嵌入和指针类型(*T)嵌入的区别

    type Student1 struct {
       Person //值类型的嵌入
    }
    type Student2 struct {
       *Person //指针类型的嵌入
    }

    要理解这个区别,就有知道go语言中类型的默认值。如下:

    1、数值类型(如int8、int16、uint等),默认值为0;

    2、布尔类型,默认值为false;

    3、字符串类型,默认值为""

    4、指针、通道、切片、字典等,默认值为nil

    5、复合类型的默认值,为所包含类型的默认值。

    所以:

    type Person struct {
       age  int8
       name string
    }
    
    func (s Person) showName() {
       fmt.Println(s.name)
    }
    
    func (s *Person) setName(newName string) {
       s.name = newName
    }
    
    type Student1 struct {
       Person //Student1包含了Person,那么Student1对应的value和pointer包含Person
    }
    
    type Student2 struct {
       *Person
    }
    
    // 内嵌类型 Persion默认值为 Person{age:0, name:""}
    s1 := Student1{}
    s1.setName("student1_01") // ok
    s1.showName()
    
    // 内嵌类型 *Persion默认值为 nil
    s2 := &Student2{}
    s2.setName("student1_02") //Error,由于目前内嵌类型的值为nil,会触发报错
    s2.showName()
    
    // 给嵌入类型一个复制,就ok了
    s3 := &Student2{Person:&Person{age:3, name:"s3"}}
    //s3 := &Student2{&Person{age:3, name:"s3"}} 和上一行等价
    s3.showName()

    在上面的案例中变量s2中嵌入类型默认值为nil,故会报错:panic: runtime error: invalid memory address or nil pointer dereference

    所以,针对指针嵌入类型,在使用前,需要赋值。

    3、嵌入和方法集的关系

    1、类型 S 包含匿名字段 T,则 S和*S 方法集包含 T 方法。

    2、类型 S 包含匿名字段 *T,则 S和 *S 方法集包含 T + *T 方法。

    3、不管嵌入的是T还是*T,*S方法集,包含 T + *T 方法。

    下面,通过案例来解析,如下,思考Student1的指针和实例类型,以及Student2的指针和实例类型,是否实现了Human接口呢?

    type Human interface { //定义接口
       showName()
       setName(string)
    }
    
    type Person struct { 
       age  int8
       name string
    }
    func (s Person) showName() { // Person类型的实例和指针,都实现了Human的showName方法
       fmt.Println(s.name)
    }
    func (s *Person) setName(newName string) { // 只有Person类型的指针,才实现了Human的setNanme方法
       s.name = newName
    }
    
    type Student1 struct { // 以值类型,嵌入
       Person
    }
    
    type Student2 struct {  // 以指针类型,嵌入
       *Person
    }

    解析:(1)应用上面规则1,由于Student1通过实体类型(T)方式,嵌入Person,所以 Stuednt1的实例类型,仅仅包含了接受者为Person的方法,即不包含setName()方法,所以Student1的实例类型,没有实现Human接口,不能赋值给Human接口;应用上面规则3, Stuednt1的指针类型,包含了接受者为Person和接受者为*Person的方法,即Stuednt1的指针类型,实现了Human接口。

    (2)应用上面规则2,由于Student2通过指针类型(*T)方式,嵌入Person,所以Student2的指针类型和实例类型,都实现了Human接口

    所以:

    // Error 应用上面的关系判断第1条规则,因为Student1实例类型的方法集中,仅仅包含Person的实例方法集,即仅仅包含showName()方法,所以Student1的实例类型,没有实现Human接口
    var s1 Human = Student1{} //报错:Student1 does not implement Human (setName method has pointer receiver)
    s1.setName("student1_01")
    s1.showName()
    
    var s2 Human = &Student1{} //ok 应用第1条和弟3条规则
    s2.setName("student1_02")
    s2.showName()
    
    var s3 Human = Student2{&Person{}} //ok ,应用第2条规则
    s3.setName("student2_01")
    s3.showName()
    
    var s4 Human = &Student2{&Person{}} //ok ,应用第2条规则
    s4.setName("student2_02")
    s4.showName()


    本文为原创文章,请尊重辛勤劳动,如需转载,请保留本文地址
    http://www.findme.wang/blog/detail/id/559.html

    若您感觉本站文章不错,读后有收获,不妨赞助一下?

    我要赞助

    您还可以分享给朋友哦

    更多
    顶
    4
    踩
    0
    • 上一篇: 公钥和私钥哪些事
    • 下一篇: go语言中的rune数据类型
    • 查看评论
    • 正在加载中...
    • 留言
    • 亲,您还没有登录,登录后留言不需要审核哦!
      可以使用如下方式登录哦!
  • CSDN | 新浪微博 | github | 关于我们 | 我要留言 | 友链申请
  • 豫ICP备18038193号    Copyright ©lidequan All Rights Reserved