第七章-接口
Go语言的结构的独特之处在于它是隐式实现。就是对于一个具体的类型,无需声明他实现了哪些接口,只要提供接口所必须的方法即可。
接口是一种抽象类型,他并没有暴露所含数据的布局或者内部结构。
一个接口类型定义了一套方法,如果一个具体类型要实现接口,那么必须实现接口类型定义中的所有方法。
实现接口:如果一个类型实现了一个接口要求的所有方法,那么这个类型实现了这个接口。
// 薪资计算器接口
type SalaryCalculator interface {
CalculateSalary() int
}
// 普通挖掘机员工
type Contract struct {
empId int
basicpay int
}
// 有蓝翔技校证的员工
type Permanent struct {
empId int
basicpay int
jj int // 奖金
}
//测试没有实现具体类型
type TestA struct {
testID int
}
func (p Permanent) CalculateSalary() int {
return p.basicpay + p.jj
}
func (c Contract) CalculateSalary() int {
return c.basicpay
}
//如果没有写TestA中的实现,main中接口数组会报错
func (d TestA) CalculateSalary() int {
return d.testID
}
// 总开支
func totalExpense(s []SalaryCalculator) {
expense := 0
for _, v := range s {
expense = expense + v.CalculateSalary()
}
fmt.Printf("总开支 $%d", expense)
}
func main() {
pemp1 := Permanent{1, 3000, 10000}
pemp2 := Permanent{2, 3000, 20000}
cemp1 := Contract{3, 3000}
abc := TestA{1}
employees := []SalaryCalculator{pemp1, pemp2, cemp1, abc} //接口数组,必须要实现该接口
totalExpense(employees)
}
空接口不包含任何方法,空接口类型对其实现类型没有任何要求,所以我们可以把任何值赋给空接口类型。
func describe(i interface{}) {
fmt.Printf("Type = %T, value = %v\n", i, i)
}
func main() {
// 任何类型的变量传入都可以
s := "Hello World"
i := 55
strt := struct {
name string
}{
name: "Naveen R",
}
describe(s)
describe(i)
describe(strt)
}
靠它才可以让fmt.Println.errorf这类的函数能够接受任意类型的参数。
非空接口类型通常由一个指针类型来实现,特别是当接口类型的一个或者多个方法暗示这个会修改接收者的情形。一个指向结构的指针才是最常见的方法接收者。
参考博文:https://www.jianshu.com/p/883cee8b0264
flag模块主要用来解析命令行的参数。
var period = flag.Duration("period", 1*time.Second, "sleep period")
func main() {
flag.Parse()
fmt.Printf("Sleeping for %v...", *period)
time.Sleep(*period)
fmt.Println()
}
接口值:一个接口类型的值有两个部分:一个具体类型和该类型的一个值。二者称为接口的动态类型和动态值。
接口值比较,需要注意得是,如果两个接口值的动态类型一致,但对应的动态值是不可比较的(比如slice),那么这个比较会以崩溃的方式失败,如;
var x interface{} = []int{1,2,3}
fmt.Println(x == x) //宕机
类型要么是可以安全比较的,要么是完全不可比较的。仅在确认接口值包含的动态值可以比较时,才比较接口值。
当处理错误或者调式时,能拿到接口值得动态类型是很有帮助的。可以使用fmt包的%T来实现这个需求:
var w io.Writer
fmt.Printf("%T\n",w) //nil
w = os.Stdout
fmt.Printf("%T\n",w) //*os.File
再内部实现中,fmt用反射来拿到接口动态类型的名字。
注意:(深坑)含有空指针的非空接口
const debug = true
func main() {
var buf *bytes.Buffer
if debug {
buf = new(bytes.Buffer) // enable collection of output
}
f(buf) // NOTE: subtly incorrect!
if debug {
// ...use buf...
}
}
// If out is non-nil, output will be written to it.
func f(out io.Writer) {
// ...do something...
if out != nil {
out.Write([]byte("done!\n"))
}
}
debug为false,会导致程序再调用os.Wirte时崩溃。原因在于 interface 的 nil 判断的特殊性,因为 interface 的结构包括两部分 type 和 data。
参考博文;https://blog.youkuaiyun.com/haodawang/article/details/80670155
http://www.do1618.com/archives/902/
error接口:它是一个接口类型,包含一个返回错误消息的方法。
类型断言:是一个作用在接口值上的操作,写出来类似于x.(T),其中x是一个接口类型的表达式,而T是一个类型(断言类型)。类型断言会检查作为操作数的动态类型是否满足指定的断言类型。
我们经常无法确定一个接口值的动态类型,这时就需要检测它是否是某一个特定类型。
var w io.Writer = os.Stdout
f,ok :=w.(*os.File) //成功:ok,f == os.Stdout
b,ok :=w.(*bytes,Buffer) //失败:!ok,b==nil
if f,ok := w.(*os.file);ok{
}
类型分支:
第一种风格:如:io.Reader、io.Wirter、fmt.Stringer、sort.Interface等,突出了满足这个接口的具体类型之间的相似性,但隐藏了各个具体类型的布局和各自特有的功能。这种风格强调方法、而不是具体类型。
第二种风格利用了接口值能够容纳各种具体类型的能力
switch x.(type){
case nil: //.......
case int,uint: //........
case bool: //......
case string: //........
default: //..........
}