Go 的方法集与接口断言

方法集

引子

首先来看一段代码:

package main

import "fmt"

func main() {
var v IpmHelloByValue
CallSayHello(v) // Ok,Output: Hello,I'm value
CallSayHello(&v) // Ok,Output: Hello,I'm value
var p IpmHelloByPointer
CallSayHello(p) // Not Ok,compile failed: IpmHelloByPointer does not implement IHello (SayHello method has pointer receiver)
CallSayHello(&p) // OK, Output: Hello,I'm pointer
} type IHello interface {
SayHello()
} type IpmHelloByPointer struct {
} func (p *IpmHelloByPointer) SayHello() {
fmt.Println("Hello,I'm pointer")
} type IpmHelloByValue struct {
} func (v IpmHelloByValue) SayHello() {
fmt.Println("Hello,I'm value")
} func CallSayHello(h IHello) {
h.SayHello()
}

为何 CallSayHello(p)会编译失败,这就涉及到方法集了。

介绍

[方法集(method set)][https://golang.org/ref/spec#Method_sets]:定义了一组关联到给定类型的值或者指针的方法。在定义方法时所使用的接收者(receiver)的类型(值/指针),决定了该方法是关联到值还是关联到指针。

Method sets

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.

The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.

类型的方法集决定了该类型所实现的接口,以及当使用该类型作为 receiver 时,所能调用的

Values Methods Receivers
T (t T)
*T (t T) and (t *T)

即:

  • T类型的,只能调用接收者类型的方法。
  • 指向T类型的指针,既能调用接收者类型指针的方法,也能调用接收者类型的方法。

例子

举个例子,为一个结构体声明两个方法,其中一个方法的 receiver 是 value,另一个方法的 receiver 是 pointer。

type MyStruct struct {
} // receiver 是一个 value
func (m MyStruct) ValueReceiver() {
fmt.Println("ValueReceiver")
} // receiver 是一个 pointer
func (m *MyStruct) PointerReceiver() {
fmt.Println("PointerReceiver")
}

为这个 struct 创建两个示例,一个的类型是 value,另一个的类型是 pointer。

func main() {
var m MyStruct // 方法集中只有 ValueReceiver()
var pm *MyStruct // 方法集中既有 PointerReceiver(), 也有 ValueReceiver()
}

接下来创建两个 interface 以及使用这两个 interface 的函数

type IValue interface {
ValueReceiver()
} type IPointer interface {
PointerReceiver()
} func CallValue(v IValue) {
v.ValueReceiver()
} func CallPointer(p IPointer) {
p.PointerReceiver()
}

分别将 m 和 pm 传入这两个函数会发生什么?

func main() {
var m MyStruct // 方法集中只有 ValueReceiver()
var pm *MyStruct // 方法集中既有 PointerReceiver(), 也有 ValueReceiver()
CallValue(m) // OK
// 因为 m 的方法集中并没有 PointerReceiver(),所以编译器说它没有实现 IPointer 接口
CallPointer(m) // Compile failed:Type does not implement 'IPointer' as 'PointerReceiver' method has a pointer receiver
CallValue(pm) // OK
CallPointer(pm) // OK
}

一个例外?

package main

import "fmt"

func main() {
var m MyStruct
m.ValueReceiver() // OK,Output: ValueReceiver
m.PointerReceiver() // OK,Output: PointerReceiver 这里为什么可以调用 PointerReceiver()?
pm := &m
pm.ValueReceiver() // OK,Output: ValueReceiver
pm.PointerReceiver() // OK,Output: PointerReceiver
} type MyStruct struct {
} // receiver 是一个 value
func (m MyStruct) ValueReceiver() {
fmt.Println("ValueReceiver")
} // receiver 是一个 pointer
func (m *MyStruct) PointerReceiver() {
fmt.Println("PointerReceiver")
}

重点在第8行,按照之前所说的,m的方法集中并没有PointerReceiver()这个方法,为何这段代码可以编译成功?

这是因为编译器在后面做了工作。

m.PointerReceiver()

这句代码中,编译器对它做了一个隐式的 dereference 操作,偷偷的将它变成了

(&m).PointerReceiver()

所以最终还是通过一个 pointer 作为 receiver 去调用的 PointerReceiver

但是当变量无法取得地址时,编译器就无能为力了,比如这种:

MyStruct{}.ValueReceiver()      // OK
MyStruct{}.PointerReceiver() // Not OK
(&MyStruct{}).PointerReceiver() // OK

因为编译器无法取得一个临时变量的地址。

接口断言

简介

接口断言可以判断一个 struct 是否实现了某个接口

通过

// 注意 _ 和 interfaceName 之间不要有 ','
var _ interfaceName = ImplementType

可以实现编译期的接口断言。

其中ImplementType既可以是一个 value,也可以是一个 pointer,如果是 value 类型,需要用 nil 来初始化。

例子

还是之前的例子:

type MyStruct struct {
} // receiver 是一个 value
func (m MyStruct) ValueReceiver() {
fmt.Println("ValueReceiver")
} // receiver 是一个 pointer
func (m *MyStruct) PointerReceiver() {
fmt.Println("PointerReceiver")
} type IValue interface {
ValueReceiver()
} type IPointer interface {
PointerReceiver()
}

加上接口断言:

var _ IValue = (*MyStruct)(nil)   // OK
var _ IPointer = (*MyStruct)(nil) // OK
var _ IValue = MyStruct{} // OK
var _ IPointer = MyStruct{} // Not OK: Type does not implement 'IPointer'

我是笨比

看起来这个接口断言好高大上呀,仔细一琢磨,它的形式不就是 go 中声明变量的方式么?

var 变量名字 类型 = 表达式

这儿只不过是把变量名字用 _ 代替了而已,意思是告诉编译器我不在乎这个变量的值。

总结:我是笨比(是什么迷惑住了我的双眼?)

参考

《Go语言实战》

《The go programming language》

https://blog.csdn.net/random_w/article/details/106279550

go 的方法集和接口断言的更多相关文章

  1. GoLang之方法与接口

    GoLang之方法与接口 Go语言没有沿袭传统面向对象编程中的诸多概念,比如继承.虚函数.构造函数和析构函数.隐藏的this指针等. 方法 Go 语言中同时有函数和方法.方法就是一个包含了接受者的函数 ...

  2. 初识 go 语言:方法,接口及并发

    目录 方法,接口及并发 方法 接口 并发 信道 结束语 前言: go语言的第四篇文章,主要讲述go语言中的方法,包括指针,结构体,数组,切片,映射,函数闭包等,每个都提供了示例,可直接运行. 方法,接 ...

  3. Go 入门 - 方法和接口

    方法和接口 方法的接受者 Go中没有类,取而代之的是在结构体上定义的方法 为了将方法(函数)绑定在某一类结构体上,我们在定义函数(方法)时引入"接受者"的概念. 方法接受者在它自己 ...

  4. golang方法和接口

    一.  go方法 go方法:在函数的func和函数名间增加一个特殊的接收器类型,接收器可以是结构体类型或非结构体类型.接收器可以在方法内部访问.创建一个接收器类型为Type的methodName方法. ...

  5. Go part 6 接口,接口排序,接口嵌套组合,接口与类型转换,接口断言

    接口 接口是一种协议,比如一个汽车的协议,就应该有 “行驶”,“按喇叭”,“开远光” 等功能(方法),这就是实现汽车的协议规范,完成了汽车的协议规范,就实现了汽车的接口,然后使用接口 接口的定义:本身 ...

  6. Go 语言入门(二)方法和接口

    写在前面 在学习 Go 语言之前,我自己是有一定的 Java 和 C++ 基础的,这篇文章主要是基于A tour of Go编写的,主要是希望记录一下自己的学习历程,加深自己的理解 Go 语言入门(二 ...

  7. Go语言_方法和接口

    方法和接口 本节课包含了方法和接口,可以用这种构造来定义对象及其行为. Go 作者组编写,Go-zh 小组翻译. https://tour.go-zh.org/methods/1 方法 Go 没有类. ...

  8. go基础_接口断言

    // interface package main import ( "fmt" ) //定义一个接口,接口名字Inter,接口的方法集有2个方法 type Inter inter ...

  9. 带你学够浪:Go语言基础系列 - 10分钟学方法和接口

    文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 对于一般的语言使用者来说 ,20% 的语言特性就能够满 ...

随机推荐

  1. Spring-04 Bean的自动装配

    Spring-04 Bean的自动装配 Bean的自动装配 1.自动装配说明 自动装配是使用spring满足bean依赖的一种方法. spring会在应用上下文中为某个bean寻找其依赖的bean. ...

  2. 最近没事DIY了个6通道航模遥控器

    在网上买了个外壳,挖空后换成自己的电路版. 开机后图: 液晶屏是320x240的,没有合适的贴纸,直接就这么用了 遥控器的内部电路有点乱哈,没办法,低成本就只能全靠跳线了 还好都能正常工作. 接收器也 ...

  3. 基于CefSharp开发浏览器(八)浏览器收藏夹栏

    一.前言 上一篇文章 基于CefSharp开发(七)浏览器收藏夹菜单 简单实现了部分收藏夹功能 如(添加文件夹.添加收藏.删除.右键菜单部分功能) 后续代码中对MTreeViewItem进行了扩展,增 ...

  4. NodeJs 入门到放弃 — 常用模块及网络爬虫(二)

    码文不易啊,转载请带上本文链接呀,感谢感谢 https://www.cnblogs.com/echoyya/p/14473101.html 目录 码文不易啊,转载请带上本文链接呀,感谢感谢 https ...

  5. 什么是内存对齐,go中内存对齐分析

    内存对齐 什么是内存对齐 为什么需要内存对齐 减少次数 保障原子性 对齐系数 对齐规则 总结 参考 内存对齐 什么是内存对齐 弄明白什么是内存对齐的时候,先来看一个demo type s struct ...

  6. Webpack 基石 tapable 揭秘

    Webpack 基于 tapable 构建了其复杂庞大的流程管理系统,基于 tapable 的架构不仅解耦了流程节点和流程的具体实现,还保证了 Webpack 强大的扩展能力:学习掌握tapable, ...

  7. Java流程控制:三种基本结构

    顺序结构: Java的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行顺序结构是最简单的算法结构语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个依次执行的处理步骤组成的, ...

  8. P1048_采药(JAVA语言)

    思路:动态规划的背包问题.把时间看作重量,转换为01背包问题求解. 题目描述 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给他出 ...

  9. 攻防世界 reverse serial-150

    serial-150 suctf-2016 直接使用ida发现main函数中夹杂大片数据,应该是自修改代码,动态调试: 调试中发现,输入为16位,验证方法为:从头开始取一字符进行比较,比较通过检验后, ...

  10. HOOK实现游戏无敌-直接修改客户端-2-使用VS来处理

    HOOK实现游戏无敌-直接修改客户端-2-使用VS来处理 大概流程 1 首先找到游戏进程,打开进程 2 申请一段内存空间来保存我们的硬编码(virtualAllocEx) 3 找到攻击函数,修改函数的 ...