go语言之接口
一:接口的基本概念
1 接口声明
接口字面量,接口命名类型,接口声明使用interface关键字。
1)接口字面量类型声明语法如下:
interface{
methodSignature1
methodSignature1
}
2)接口命名类型使用type关键字声明
type interfaceName interface {
MethodSignature1
MethodSignature2
}
接口定义大括号内可以是方法声明的集合,也可以嵌入另一个接口类型匿名字段,还可以是二者的混合。
接口支持嵌入匿名接口宇段,就是一个接口定义里面可以包括其他接口, Go编译器会自动进行展开 理,
type Reader interface {
Read(p []byte ) (n int , err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
type ReadWr ter interface {
Reader
Wr te(p []byte) (n int, err error)
}
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n nt err error)
}
3)方法声明
MethodName (InputTypeList)OutputTypeList
4)声明新接口类型的特点
(I)接口的命名一般以“er ”结尾
()接口定义的内部方法声明不需要 func 引导。
()在接口定义中,只有方法声明没有方法实现。
5) 接口的定义与实现
package main import (
"fmt"
) type Humaner interface {
//方法
Say()
}
//学生结构体
type Student struct {
name string
score int
} func (s*Student) Say() {
fmt.Println("Student[%s,%d]瞌睡不断\n",s.name,s.score)
}
type Teacher struct {
name string
group string
} func (t *Teacher) Say() {
fmt.Println("Teacher[%s,%s] 诲人不倦\n",t.name,t.group)
}
//自定义类型
type Mystr string func (str Mystr) Say() {
fmt.Println("Mystr[%s] 统治醒醒,还有个bug\n",str)
}
//参数为接口类型
func Whosay(i Humaner) {
i.Say()
}
func main(){
s :=&Student{"学生",}
t :=&Teacher{"老师","GO语言"}
var tmp Mystr="字符串"
s.Say()
t.Say()
tmp.Say()
//多态,条用同一接口不同的表现
Whosay(s)
Whosay(t)
Whosay(tmp) //make()
x :=make([]Humaner,)
x[],x[],x[] = s,t,tmp
for _,value :=range x{
value.Say()
}
}
接口的继承
package main import "fmt"
//定义接口
type Humaner interface {
//方法
Say()
}
type Personer interface {
//相当于写了say() 方法的继承
Humaner
//唱歌
Sing(lyrics string)
}
type Student struct {
name string
score int
}
func (s *Student) Say() {
fmt.Printf("Student[%s,%d] 瞌睡不断\n",s.name,s.score) //Student[学生,80] 瞌睡不断
} func (s *Student) Sing(lyrics string){
fmt.Printf("Student sing[%s]!!\n",lyrics) //Student sing[葫芦娃]!!
}
func main() {
s := &Student{"学生",}
//调Personer方法
var p Personer
p = s
p.Say()
p.Sing("葫芦娃")
}
2 接口初始化
单纯地声明一个接口变量没有任何意义,接口只有被初始化为具体的类型时才有意义。接口作为
一个胶水层或抽象层,起到抽象和适配的作用 。没有初始化的接口变量,其默认值是 nil。
3 接口绑定具体类型的实例的过程称为接口初始化。接口变量支持两种直接初始化方法
1)实例赋值接口
如果具体类型实例的方法集是某个接口的方法集的超集,则称该具体类型实现了接口,可
以将该具体类型的实例直接赋值给接口类型的变 ,此时编译器会进行静态的类型检查。接口
被初始化后,调用接口的方法就相当于调用接口绑定的具体类型的方法,这就是接口调用的语义。
2)接口变量赋值接口变量
已经初始化的接口类型变量a直接赋值给另一种接口变量b ,要求b的方法集是a的方法即
的子集 此时 Go 编译器会在编译时进行方法集静态检查 这个过程也是接口初始化的一种
方式,此时接口变量 绑定的具体实例是接口变量 绑定的具体实例的副本。
file ,_ := os .OpenFile (” notes.txt”, os.O_RDWR |os.O CREATE , )
var rw io .ReadWriter = file
//io.ReadWriter 口可以直接赋位给 io.Writer接口变量
var w o.Writer = rw
4 接口方法的调用
接口方法调用和普通的函数调用是有区别的。接口方法调用的最终地址是在运行期决定的,
将具体类型变量赋值给接口后,会使用具体类型的方法指针初始化接口变量,当调用接口变量的方法时,
实际上是间接地调用实例的方法。接口方法调用不是 种直接的调用,有 定的运行时开销
直接调用禾初始化的接口变 的方法会产生 panic 。
package main
type printer interface {
Print()
}
type S struct {}
func (s S) Print() {
println("print")
}
func main() {
var i printer
//没有初始化的接口调用其他方法会产生panic
//必须初始化
i = S{}
i.Print()
}
5 接口动态类型和静态类型
1)动态类型
接口绑定的具体实例的类型称为接口的动态类型。接口可以绑定不同类型的实例,所以接
口的动态类型是随着其绑定的不同类型实例而发生变化的。
2) 静态类型
接口被定义时, 其类型就已经被确定 这个类型 接口的静态类型。接口的静态类型在其
定义 就被确定,静态类型的本质特征就是接口的方法签名集合。两个接口如果方法签名集合
相同(方法的顺序可以不同),则这两个接口在语义上完全等价,它们之间不需要强制类型转换就可以相互赋值。
原因是 Go 编译器校验接口是否能赋值,是比较二者的方法集,而不是看具体接口类型名。
二: 接口运算
1 语法:
i.(TypeNname)
i必须是接口变 ,如果是具体类型变量,则编译器会报 on interface type xxx on left,
TypeNname 可以是接口类型名,也可以是具体类型名。
2 接口查询的两层含义
(1)如果 TypeNname 是一个具体类型名,则类型断言用于判断接口变量 绑定的实例类
型是否就是具体类型 TypeNname
(2)如果 TypeName 是一个接口类型名,则类型断言用于判断接口变量 绑定的实例类型
是否同时实现了 TypeName 接口。
3 接口断言的两种语法表现
直接赋值模式
o := i.(TypeName)
语义分析:
() TypeNam 是具体类型名,此时如果接 绑定的实例类型就是具体类型 TypeName,
变量 。的类型就是 TypeName 变量。的值就是接口绑定的实例值的副本(当然实例可能是
指针值,那就是指针值的副本)
() TypeName 是接口类型名 如果接口i绑定的实例类型满足接口类型 TypeName ,则变量o
的类型就是接口类型 TypeName,o底层绑定的具体类型实例是i绑定的实例的副本(当然实例可能是指针值,那就是指针值的副本〉。
()如果上述两种情况都不满足, 则程序抛 panic
示例
package main import "fmt" type Inter interface {
Ping()
Pang()
}
type Anter interface {
Inter
String()
}
type St struct {
Name string
} func (St) Ping() {
println("ping")
}
func (*St) Pang() {
println("pang")
}
func main() {
st := &St{"andes"}
var i interface{}=st
//判断i绑定的实例是否实现了接口类型Inter
o :=i.(Inter)
o.Ping()
o.Pang()
//如下语句会引发panic,因为i没有实现接口Anter
//p :=i.(Anter)
//p.String()
//判断 i绑定的实例是否就是具体类型St
s := i.(*St)
fmt.Printf("%s",s.Name)
}
4 comma,ok 表达模式如下
if o,ok :=i.(TypeName);ok{
}
语法分析
()TypeName是具体类型名,此时如果接口i绑定的实例类型就是具体类型TypeName,则ok为true变量。变量o的类型就是TypeName,
变量o的值就是接口绑定的实例值的副本(当然实例可能是指针值,那就是指针值的副本)
()TypeName是接口类型名,此时如果接口i绑定的实例类型满足接口类型TypeName,则ok为true,变量o的类型就是接口类型
TypeName,o底层绑定的具体类型实例是i绑定的实例的副本(当然实例可能是指针值,那就是指针值的副本)。
()如果上述两个都不满足,则 ok为 false 变量o是TypeName 类型的“零值”,此种条
件分支下程序逻辑不应该再去引用o,因为此时的o没有意义
示例:
package main import (
"fmt"
) type Inter interface {
Ping()
Pang()
}
type Anter interface {
Inter
String()
}
type St struct {
Name string
}
func (St) Ping(){
println("ping")
}
func (*St) Pang(){
println("pang")
}
func main(){
st := &St{"andes"}
var i interface{} = st
//判断i绑定的实例是否实现了接口类型Inter
if o,ok := i.(Inter);ok{
o.Ping() //ping
o.Pang() //pang
}
if p,ok := i.(Anter);ok{
//i没有实现接口Anter,所以程序不会执行到这里
p.String()
}
//判断i 绑定的实例是否就是具体类型St
if s,ok := i.(*St);ok{
fmt.Printf("%s",s.Name) //andes
}
}
5 类型查询
语法格式:
switch v :=工. (type) {
case typel :
xx xx
case type2 :
xx xx
default :
xx xx
语义分析:
语义:
查询一个接口变量底层绑定的底层变量的具体类型是什么,
查询接口变量绑定的底层变量是否实现了其他接口
1)i 必须是接口类型
描述:
具体类型实例的类型是静态的 在类型声明后就不再变化,所 具体类型的变量不存在类
型查询 类型查询一定是对一个接口变量进行操作。也 就是说,上文中的i必须是接口变
如果 是未初始 接口变量,则的值是nil 。
var i io.Reader
switch v := i.(type) { //此处i是为未初始化的接口变量,所以v为nil
case nil :
fmt.Printf( " %T\n ”,v ) //<nil>
default :
fmt.Printf (”default”)
}
( 2 ) case 字句后面可 m~ 非接口类型名,也可以跟接口类型名,匹配是按照 case 子句的
顺序进行的。
如果 case 后面是一个接口类型名,且接口变量 绑定的实例类型实现了该接口类型的方法
,则匹配成功,v的类型是接口类型,v底层绑定的实例是 绑定具体类型实例的副本。
示例:
package main import (
"io"
"log"
"os"
) func main() {
f,err := os.OpenFile ("notes.txt",os.O_RDWR|os.O_CREATE, )
if err != nil {
log.Fatal(err)
}
defer f.Close()
var i io.Reader = f
switch v :=i.(type) {
//i的绑定的实例是*osFile类型,实现 io.ReadWriter接口,所以case匹配成功
case io.ReadWriter:
//v是io.ReadWriter 接口类型,所以可以调用Write方法
v.Write( []byte ("io.ReadWriter\n" ))
//由于上一个case 已经匹配,就算这个case 也匹配,也不会走到这里
case *os.File:
v.Write ([]byte ("*os.File\n"))
v.Sync ()
default:
return
}
}
如果case后面跟着多个类型,使用逗号分隔,接口变量i绑定的实例类型只要和其中一个类型匹配,
则直接使用o赋值给 v,相当于v := o 这个语法有点奇怪,按理说编译器不应该允许这种操作,
语言实现者可能想让 type switch 语句和普通的 sw itch 语句保持一样的语法规则,允许发生这种情况。
package main import (
"fmt"
"io"
"log"
"os"
) func main(){
f,err := os.OpenFile("notes1.txt",os.O_RDWR|os.O_CREATE,)
if err != nil {
log.Fatal(err)
}
defer f.Close()
var i io.Reader = f
switch v := i.(type) {
//多个类型,f满足其中任何一个就算匹配
case *os.File,io.ReadWriter:
//此时相当于执行v :=i ,v和i是等价的,使用v没有意义
if v==i{
fmt.Println(true) //true
}
default:
return
}
}
6 标准库的使用
格式:
switch i := i.(type) {
}
类型查询和类型断言
()类型查询和类型断言具有相同的语义,只是语法格式不同。 二者都能判断接口变量绑
定的实例的具体类型,以及判断接口变量绑定的实例是否满足另一个接口类型。
()类型查询使用 case 字句一次判断多个类型,类型断言一次只能判断一个类型,
当然类型断言也可以使用 if else if 语句达到同样的效果
7 接口优点和使用形式
接口优点
(1)解祸:复杂系统进行垂 和水平的分割是常用的设计手段,在层与层之间使用接口进
行抽象和解辑是 种好的编程策略 Go 的非侵入式的接口使层与层之间的代码更加干净,
具体类型和实现的接口之间不需要显式声明,增加了接口使用的自由度
(2)实现泛型:由于现阶段Go语言还不支持泛型,使用空接口作为函数或方法参数能够用在需要泛型的场景中
接口的使用形式
()作为结构 嵌字段。
()作为函数或方法的形参。
()作为函数或方法的返回值。
()作为其他接口定义的嵌入宇段。
三: 空接口
概述:
没有任何方法的接口,我们称之为空接 。空接口表示为 interface{}
用途:
空接口和泛型
Go 语言没有泛型, 如果一个函数需要接收任意类型的参数, 则参数类型可以使用空接口,这是弥补没有泛型的一种手段
//典型的就是 fmt 标准 里面的 print 函数
func Fprint (w io.Writer, a . . . interface(}) (n int, err error)
空接口和反射
空接口是反射实现 基础 反射库就是将相关具体的类型转换并赋值给空接 后才去处理,
1 空接口和nil
空接口不是真的为空,接口有类型和值两 概念
示例
package main
import (
"fmt"
)
type Inter interface {
Ping()
Pang()
}
type St struct {} func (St) Ping(){
println("ping")
}
func (*St) Pang(){
println("pang") //pamg
}
func main(){
var st *St = nil
var it Inter = st fmt.Printf("%p\n",st) //0x0 fmt.Printf("%p\n",it) //0x0 if it !=nil {
it.Pang()
//下面的语句会导致 panic
//方法转换为函数调用,第 一个参数是St类型,由于 St是nil ,无法获取指针所指的
//对象佳,所以导致 panic
//it.Ping
}
}
comma-ok断言
package main import (
"fmt"
) //空接口 type Element interface {} type Person struct {
name string
age int
} func main() {
//3容量的切片
list := make([]Element,)
list[] = //int
list[]="Hello" //string
list[] = Person{"zhangsan",}
for index,element := range list {
//类型断言: value,ok =element,(T)
if value,ok :=element.(int);ok {
fmt.Printf("list[%d]是int类型,值是%d\n",index,value) //list[0]是int类型,值是1
}else if value,ok := element.(string);ok {
fmt.Printf("list[%d]是string类型,值是%s\n",index,value) //list[1]是string类型,值是Hello
}else {
fmt.Printf("list[%d]是其他类型\n",index) //list[2]是其他类型
}
}
}
switch 接口测试
package main import "fmt"
//空接口
type Element interface{} type Person struct {
name string
age int
}
func main() {
list := make([]Element, )
list[] = //int
list[] = "Hello" //string
list[] = Person{"zhangsan", }
for index,element := range list{
switch value := element.(type) {
case int :
fmt.Printf("list[%d]是int类型,值是%d\n",index,value)
case string:
fmt.Printf("list[%d]是string类型,值是%s\n",index,value)
default:
fmt.Printf("list[%d]是其他类型\n",index)
}
}
}
四: 接口的内部实现(这个涉及底层很多东西,我不会)
go语言之接口的更多相关文章
- R语言数据接口
R语言数据接口 R语言处理的数据一般从外部导入,因此需要数据接口来读取各种格式化的数据 CSV # 获得data是一个数据帧 data = read.csv("input.csv" ...
- [日常] Go语言圣经--接口约定习题
Go语言圣经-接口1.接口类型是对其它类型行为的抽象和概括2.Go语言中接口类型的独特之处在于它是满足隐式实现的3.Go语言中还存在着另外一种类型:接口类型.接口类型是一种抽象的类型4.一个类型可以自 ...
- FFI (语言交互接口(Foreign Function Interface))
FFI(Foreign Function Interface)是用来与其它语言交互的接口, 在有些语言里面称为语言绑定(language bindings), Java 里面一般称为 JNI(Java ...
- C语言原子接口与实现
原子是一个指向唯一的.不可变的0个或任意多个字节序列的指针,大多数原子都是指向以空字符结束的字符串,但是任何一个指向任意字节序列的指针都可以使原子.任何原子只能出现一次.如果两个原子指向同一个内存单元 ...
- c语言调试接口
http://blog.chinaunix.net/uid-10106787-id-2985587.html 在C语言程序设计中,常会出现各种各样的bug:段错误.参数异常等等.我们需要尽快定位错误, ...
- Java语言Socket接口用法详解
Socket接口用法详解 在Java中,基于TCP协议实现网络通信的类有两个,在客户端的Socket类和在服务器端的ServerSocket类,ServerSocket类的功能是建立一个Serve ...
- Go语言的接口
一.接口的定义和好处 我们都知道接口给类提供了一种多态的机制,什么是多态,多态就是系统根据类型的具体实现完成不同的行为. 以下代码简单说明了接口的作用 package main import ( &q ...
- go语言学习-接口
Go语言中虽然没有传统面向对象语言中类.集成的概念,不过提供了接口的支持,可以使用接口来使用一些面向对象的特性. 在 go 语言中,的接口有下面几个特点: 可以包含0个或多个方法的签名 只定义方法的签 ...
- Go语言的接口interface、struct和组合、继承
Go语言的interface概念相对于C++中的基类,通过interface来实现多态功能. 在C++中,当需要实现多态功能时,步骤是首先定义一个基类,该基类使用虚函数或者纯虚函数抽象了所有子类会用到 ...
- Go语言的接口与反射
美女图片没啥用,就是为了好看 本文还在完善中... go总体而言是一门比较好入门的语言,许多特性都很精简易懂,但是接口与反射除外.他们真的让人头疼,不知道是自身资质问题还是怎么着,总是觉得很多书上写的 ...
随机推荐
- PHP usleep() 函数
实例 延迟执行当前脚本 5 秒(5000000 微秒):高佣联盟 www.cgewang.com <?php echo date('h:i:s') . "<br>" ...
- PHP strstr() 函数
实例 查找 "world" 在 "Hello world!" 中是否存在,如果是,返回该字符串及后面剩余部分: <?php echo strstr(&qu ...
- K近邻算法(一)
K 近邻算法思想: 寻找该点周围最近的K个点.根据这K 个点的类别来判断该点的类别: 核心: 数据归一化.(在必要的时候必须进行数据归一化处理,防止某一特征在计算数据时占比较重) 计算欧拉距离 . 使 ...
- SEO(Search Engine Optimization)优化
SEO(Search Engine Optimization)汉议为搜索引擎优化,是一种利用搜索引擎的规则提高网站在有关搜索引擎内自然排名的方式. SEO的目的是对网站进行深度的优化,从而帮助网站获取 ...
- 深入探究JVM之方法调用及Lambda表达式实现原理
@ 目录 前言 正文 解析 分派 静态分派 动态分派 单分派和多分派 动态分派的实现 Lambda表达式的实现原理 MethodHandle 总结 前言 在最开始讲解JVM内存结构的时候有简单分析过方 ...
- Linux(Centos 7)下安装Git并配置连接GitHub
1.安装git Centos7 查看git --version 2.配置用户名密码 git config --global user.name "xxx" git config ...
- 字段解析之OopMapBlock(4)
OopMapBlock是一个简单的内嵌在Klass里面的数据结构,用来描述oop中包含的引用类型属性,即该oop所引用的其他oop在oop中的内存分布,然后就可以根据当前oop的地址找到所有引用的其他 ...
- 【API进阶之路】无法想象!大龄码农的硬盘里有这么多宝藏
摘要:通过把所需建立的工具库做成云容器化应用,用CCE引擎,通过API网关调用云容器引擎中的容器应用.不仅顺应了云原生的发展趋势,还能随时弹性扩容,满足公司规模化发展的需求. 公司开完年中会后,大家的 ...
- Java—增强for循环与for循环的区别/泛型通配符/LinkedList集合
增强for循环 增强for循环是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的. 它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作. ...
- C#LeetCode刷题-回溯算法
回溯算法篇 # 题名 刷题 通过率 难度 10 正则表达式匹配 18.8% 困难 17 电话号码的字母组合 43.8% 中等 22 括号生成 64.9% 中等 37 解数独 45.8% ...