go学习笔记(类型二interface)

interface

接口是用来定义行为的一种类型,被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。不需要显示的声明实现了哪个接口,只要实现了接口中的一组方法就认为该类型实现了接口,换而言之就是隐式的实现,如果用户定义的类型实现了接口的一组方法,那么就会把用户定义的类型的值存入这个接口类型的值。

对接口值方法的调用就会调用起接口值里存储的用户定义类型的值的方法。

//  定义一个接口
type error interface {
    Error() string
}

type Error struct {
    Exception Exception
    Message   string
}

//  error 实现了error接口中的Error方法,此时Error类型中的值就会存入接口类型的值里。
func (this *Error) Error() string {
    return this.Message
}

Interface是一种类型,不是任意类型。interface有2种,一种是带方法的,另一种是不带方法的空接口interface{}。

package main

func main() {
    if err := preference.Load(preference.PreferenceGeneral); err != nil {
        log.Fatal("加载失败")
    }
}

//  这里的p并不等于preference.PreferenceGeneral,他们是不同的类型。
//  所有调用Load方法传进来的参数都将隐式的转为interface类型。
func Load(p interface{}) error {
    var err error
    key := reflect.TypeOf(p).Name()

    var value bytes.Buffer
    dec := gob.NewDecoder(&value)
    if value, err = LoadPreference(key); err != nil {
        return err
    }
    dec.Decode(p)
    return nil
}

接口值是一个两个字节长度的数据结构,第一个字节包含一个指向内部表(iTable)的指针,iTable包含了所存储的值的类型信息以及与这个值所关联的一组方法,另一个字节是一个指向所存储值的指针。

image-20200224192359332.png

user类型值赋给接口值后,第一个字节itable里存储了user的类型以及方法集,如果是n=&user{19,"chujiu"}则itable里的user类型信息则是一个指向user类型信息的指针和方法集,第二个字节存储指向user值的指针。

go对不同的interface类型是否包含一组方法做了不同的处理,使用iface结构体表示包含方法的interface,eface表示空interface。

//  包含具体方法的interface
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype // 表示接口类型
    _type *_type    //  表示具体的类型,也就是接口值中存储的具体的类型
    hash  uint32 // 对 _type.hash 的拷贝,当我们想将 interface类型转换成具体类型时,可以使用该字段快速判断目标类型和具体类型 _type 是否一致
    _     [4]byte
    fun   [1]uintptr // 是一个动态大小的数组,它是一个用于动态派发的虚函数表,存储了一组函数指针。虽然该变量被声明成大小固定的数组,但是在使用时会通过原始指针获取其中的数据,所以 fun 数组中保存的元素数量是不确定的
}

//  空的interface
type eface struct {
    _type *_type
    data  unsafe.Pointer
}

type _type struct {
    size       uintptr  //  存储类型占用的内存空间,为内存空间的分配提供信息
    ptrdata    uintptr 
    hash       uint32   //  根据hash值快速判断出类型是否相等
    tflag      tflag
    align      uint8
    fieldAlign uint8
    kind       uint8
    equal      func(unsafe.Pointer, unsafe.Pointer) bool // 用于判断当前类型的多个对象是否相等
    gcdata     *byte
    str        nameOff
    ptrToThis  typeOff
}

方法集

type notifier interface {
    notify()
}

type user struct {
    name string
  age uint
}

func (m *user) notify() {
    fmt.Println(user.name);
}

func main() {
  u:= user{"chujiu",19}
  send(u) // Cannot use 'u' (type user) as type notifier Type does not implement 'notifier' as 'notify' method has a pointer receiver 
}

func send(n notifier) {
  n.notify()
}

上面是一个例子,send(u)这里报错了,这是因为在调用send方法时,参数都会转为接口类型n,在这里u是一个值类型,而u呢并没有实现接受者为值类型的接口方法。

方法集定义了一组关联到给定类型的值或者指针的方法,定义方法时使用的接受者类型决定了这个方法时关联到值还是关联到指针,或者是两个都关联。

GO语言规范里定义的方法集的规则:

  1. 从值的角度来看
Values Methods receives
T (t T)
*T (t T) and (t *T)

T类型的值的方法集只包含值类型接受者声明的方法。

*T类型的值的方法集包含值类型接受者声明的方法以及指针类型接受者声明的方法。

  1. 从接受者的角度来看
Methods receives Values
(t T) T and *T
(t *T) *T

如果使用指针类型(*T)作为接受者来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口。如果使用值(T)接受者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口。

举个例子:

//  user在这里是指针接受者,那么只有指向user的指针才能够实现对应的接口。
//  u:= user{"chujiu",19}
//  send(u) 不能使用u的原因就是因为我们使用u的指针实现了接口,在这里确传了个u的值进去。必须得传u的地址.
//  send(&u)
func (m *user) notify() {
    fmt.Println(user.name);
}
//  user在这里是指针接受者,那么只有指向user的指针才能够实现对应的接口。
//  u:= user{"chujiu",19}
//  send(&u) 或者send(u)都是可以的。
func (m user) notify() {
    fmt.Println(user.name);
}

嵌入类型

go允许用户扩展或者修改已有的类型,嵌入类型就是将已有的类型直接声明在新的结构类型里。被嵌入的类。通过嵌入类型,那么内部类型的相关的标识符会提升到外部类型上,和直接声明在外部类型里的标识符一样。而外部类型也可以通过声明和内部类型标识符同名的标识符来覆盖内部的标识符的字段或者方法。

举个栗子:

type notifier interface {
    notify()
}

type user struct {
    name string
    age  uint
}

func (u *user) notify() {
    fmt.Println(u.name)
}

type admin struct {
    user
    level string
}

func main() {
    admin := admin{
        //  一般如果类型的字段少于两个或者三个,可以省略字段初始化,多的建议写上对应的字段,代码看起来会更加明了
        user:  user{ "chujiu", 19},
        level: "超级管理员",
    }
    
  //    内部类型user的notify方法被提升到了外部。
    admin.notify()
  //    可以直接访问内部类型的方法
    admin.user.notify()
  
  //    admin并没有实现notifier接口,但由于嵌入类型user实现了notify方法。
  //    内部类型的提升导致内部类型实现的接口也提升到外部类型,这就意味着外部类型也实现了notifier接口。
  send(&admin)
}

func send(n notifier) {
  n.notify()
}

如果外部类型也实现了notifier接口,那么内部类型实现的notify方法就不会提升。

func (ad *admin) notify() {
    fmt.Println(ad.level)
}
func main() {
    admin := admin{
        //  一般如果类型的字段少于两个或者三个,可以省略字段初始化,多的建议写上对应的字段,代码看起来会更加明了
        user:  user{ "chujiu", 19},
        level: "超级管理员",
    }
    send(&admin) // 输出超级管理员
}

公开/未公开标识符

在go语言当中一个标识符是否是公开的,是用大小写区分的。大写就是公开的,而小写就是未公开的。

举个栗子:

//  preference/preference.go
//  当要写代码属于某个包时,最好时使用与代码所在目录一样的名称作为包名。
package preference

//  priceType小写开头表示未公开的
type priceType int

//  公开的类型
type Price int

//  公开的方法
//  虽然priceType是未公开的,但NewPriceTyep方法是公开的,通过这个方法返回一个priceType类型的变量,却是可以编译通过,运行的。
func NewPriceTyep(value int) priceType{
  return priceType(value)
}

type user struct {
    Name string
    Age  uint
}

type Admin struct {
    user
    Level string
}
func main(){
  ad := preference.admin{
    Level: "超级管理员",
    user: { //  这个是错误的,因为user是不公开的,不能初始化。
      Name:"chujiu",
      Age:19,
    },
  }  
  //    这样是错误的,因为user不是公开的。
  ad.user.Name = "chujiu"
  
  //    这样却是可以的,user虽然是不公开的,但里面的字段是公开的,内部类型的字段会提升到外部,所以这样是ok的。
  ad.Name = "chujiu"
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278

推荐阅读更多精彩内容