目录
前言
本专栏是笔者在学习《Go程序设计语言》这本书时,对每个章节认为较为重要(容易忘记👻)的知识点记录的笔记,其中也会有少量的思考👀, 现整理成博客分享出来。
如果对专栏感兴趣,跑过去看一眼,书中的每一章都有:《Go程序设计语言》笔记
❗️注意❗️:本专栏不是详细的知识讲解,只是碎片的知识条目,或可作为Go知识点查漏补缺的小工具~
- 接口类型是对其他类型行为的概括与抽象;
Go
语言的接口是隐式实现的,对于一各具体类型,无需声明它实现了哪些接口,只要提供接口所必须的方法即可;
接口即约定
-
接口是一种抽象类型,它没有暴露所含的数据布局或内部结构,它仅提供一些方法;
-
如
io.Writer
:package io type Writer interface{ // 注意此处不需要 func Write(p []byte)(n int, err error) }
接口类型
- 如果一个具体类型要实现该接口,那么必须实现接口类型中定义的所有方法;
- 可以将接口组合,得到新接口,如
Reader/Writer/Closer
可以组合为ReadWriter、ReadWriterCloser
; - 接口组合可以使用嵌入式接口的语法,类似于匿名对象;也可以提供需要组合的接口的所有方法,隐式实现那个接口;也可以混用两种方法;
- 嵌入式接口、隐式实现接口、混用组合接口这几种方式声明的接口效果是一样的;
- 接口中方法定义的顺序是无意义的,有意义的只有接口的方法集合;
实现接口
- 空接口类型
interface{}
,它对类型没有任何要求,所以可以把任何类型赋值给空接口类型; - 非空的接口类型通常由一个指针类型来实现,特别是当接口类型的方法暗示会修改接收者的情形;
- 从具体类型出发,提取其共性后可以表示为一种接口类型;
使用flag.Value来解析参数
-
想支持自定义类型,需要定义一个满足
flag.Value
接口的类型:package flag type Value interface{ String() string // 用于格式化自定义类型的值 Set(string) error // 用于由字符串解析为类型对象 } package mypackage // 自定义的类型,希望能支持命令行解析 type MyInt int // 实现Value接口 func (m MyInt)Set(s string)error{ //... } func (m MyInt)String()string{ //... } // 声明变量 var myint MyInt func Test(){ // 使用时 // 将变量绑定到控制行变量中,提供使用 标志my 和 用法说明 flag.CommandLine.Var(&myint, "my", "测试解析自定义类型") flag.Parse() fmt.Println(myint) }
接口值
- 一个接口类型的值有两个部分:一个具体类型,和该类型的一个值;分别称为动态类型和动态值;
- 接口的零值就是把它的动态类型和值都设为
nil
; - 一般来说,编译时无法知道一个接口值的动态类型是什么,所以通过接口来做调用必然需要使用动态分发:编译器必须生成一段代码,来从类型描述符拿到对应接口的方法地址,再间接调用该接口的方法;
- 接口值可以使用
==
和!=
操作符来做比较,当 都为nil
或 动态类型和动态值均相等 时==
成立; - 接口值可比较,因此可以作为
map
的键或是switch
的操作数; - 但如果接口值的动态类型一致,但值不可比较,则导致宕机;所以仅在确定动态值可比较时才使用
==
或!=
; - 有时候动态类型不是
nil
,但动态值为nil
,调用接口时将导致宕机;
使用sort.Interface来排序
-
sort
包提供针对任意序列,根据任意排序函数进行原地排序的功能;package sort type Interface interface{ Len() int // 获取序列长度 Less(i, j int) bool // 比较i,j下标对应元素的大小 Swap(i, j int) // 交换下标为i,j的两个元素 }
http.Handler接口
http.HandlerFunc
是一个函数类型,同时也满足接口http.Handler
,它的ServeHTTP
方法就是调用函数本身;Web
服务器每次都用一个新的goroutine
来调用处理程序,所以处理程序必须要注意并发问题;
error接口
-
error
实际上只是一个接口类型:type error interface{ Error() string }
-
构造
error
最简单的方法是调用errors.New
,返回一个包含指定字符串的error
; -
最常用的方法是
fmt.Errorf()
格式化打印一个error
; -
errors
包只有4
行:package errors func New(text string)error {return &errorString{text}} type errorString struct { s string} func (e *errorString)Error() string {return e.s}
示例:表达式求值器
类型断言
- 类型断言是一个作用在接口值上的操作,写出来类似于
x.(T)
,其中x
是一个接口类型的表达式,而T
是一个类型;类型断言会检查动态类型是否满足指定的断言类型; - 如果操作数是一个空接口值,类型断言都失败;
- 断言成功时返回一个
T
类型的接口对象; - 由于经常无法确定接口值的动态类型,所以需要检测它是否是某一个特定类型;
- 向更宽松的接口(方法更少)做断言相当于赋值,一般情况下都是向接口更多的类型进行断言;
b := a.(B)
形式的断言,失败时宕机;b, ok := a.(B)
形式的断言,失败时不会宕机,而是返回的ok==false
;
使用类型断言来识别错误
I/O
会因为很多原因失败,但有3
类原因通常必须单独处理:文件已创建、文件没找到、权限不足;os
包提供了三个帮助函数:os.IsExist(err error) bool
、os.IsNotExist(err error) bool
、os.IsPermission(err error) bool
,用来判断错误类型是不是文件I/O
时的以上三种错误;- 对于文件
I/O
时产生的错误,可以使用以上函数判断是否为具体的错误;
通过接口断言来查询特性
web
服务器响应时,需要w.Write([]byte(str))
,其中的[]byte
转换需要进行内存分配和复制,但复制后的内存又会被马上抛弃;如何来避开内存分配?使用w.Writestring(str)
;- 使用类型断言来判定一个更普适接口类型的值是否满足一个更专用的接口类型,如果满足,则可以使用后者定义的方法;这种技术不仅适用于标准接口,而且适用于自定义接口;
- 但是使用自定义接口来断言时,需要确定该接口的实现与预想效果一致;
类型分支
- 接口有两种不通风格:子类型多态、特设多态;
- 子类型多态:接口各种方法突出满足该接口的具体类型之间的相似性,但隐藏各个具体类型的布局和各自特有功能;这种风格强调了方法,而不是具体类型;
- 特设多态:冲锋利用接口值能容纳各种具体类型的能力,把接口作为这些类型的联合来使用,使用类型断言来在运行时区分并分别处理;称为可识别联合;
- 类型分支:
switch x.(type)
;type
是关键字;分支按顺序判断;
示例:基于标记的XML解析
一些建议
- 设计一个新包时,新手
Go
程序员容易:先创建一系列接口,然后定义满足这些接口的具体类型;这样容易产生很多接口,且是不必要的抽象; - 可以利用导出机制来限制一个类型的哪些方法或字段是对包外可见的,仅在有2个或多个具体类型需要按统一的方式处理时才需要接口;
如有错误 ❌ ,欢迎指正 ☝️~
如有收获 🍗,可以考虑点赞👍/评论💬/收藏⭐️/关注👀,大家共同进步~