从零带你学习Go语言:19、Go接口与类型断言

Go接口与类型断言

Go中接口的用途和C#中的接口类似。

一个接口是特定值预期具有的一组方法。一个接口类型是一个抽象类型。接口并不会描述值是什么:它们不会说基础类型是什么或者数据是如何保存的。它们仅仅描述值能做什么:它具有哪些方法。

定义接口

使用interface关键字定义一个接口类型,后面跟着一个花括号,内部含有一组方法,以及方法期望的参数和返回值。

type Player interface {
	Play(string)
	Stop()
}

接口类型的规则和其他类型相同。如果名称以小写字母开头,接口类型不会被导出并且不会在当前包之外被访问。

实现接口

在C#中,实现一个接口需要显式指定,而在Go语言中,这是自动发生的。(接口满足是自动的。不需要显式声明具体类型满足Go中的接口。)

实现接口的类型,需要满足的条件:该类型必须实现接口中定义的所有方法,并且这些方法名称、参数类型(可能没有)和返回值(可能没有)都必须和接口中定义的一致。

任何拥有接口定义的所有方法的类型被称作满足那个接口。

除了接口中列出的方法之外,类型还可以有更多的方法,但是它不能缺少接口中的任何方法,否则就不满足那个接口。

一个类型可以满足多个接口,一个接口(通常应该)可以有多个类型满足它。

//声明一个接口类型
type MyInterface interface {
	MethodWithoutParameters()
	MethodWithParameter(float64)
	MethodWithReturnValue() string
}

//定义MyType类型,实现MyInterface接口
type MyType int

func (m MyType) MethodWithoutParameters() {
	fmt.Println("MethodWithoutParameters被调用")
}

func (m MyType) MethodWithParameter(f float64) {
	fmt.Println("MethodWithParameter", f)
}

func (m MyType) MethodWithReturnValue() string {
	return "MethodWithReturnValue被调用"
}

//一个类型即使有额外的不属于接口的方法,但它仍然可以满足接口
func (m MyType) MethodNotInInterface() {
	fmt.Println("该方法不在接口方法内")
}

如果一个类型包含接口中声明的所有方法,那么它可以在任何需要接口的地方使用,而不需要更多的声明。

接口类型的变量

一个接口类型的变量能够保存任何满足接口的类型的值。

import (
	"book_head_first_go/ch11/mypkg"
	"fmt"
)

func main() {
	//声明一个接口类型的变量
	var value mypkg.MyInterface
	//将MyType类型的值赋值给该接口变量(MyType满足MyInterface接口)
	value = mypkg.MyType(5)
	//调用满足该接口的对应的方法
	value.MethodWithoutParameters()
	value.MethodWithParameter(11.1)
	fmt.Println(value.MethodWithReturnValue())
}

也可以将函数的参数定义为接口类型。

type NoiseMaker interface {
	MakeSound()
}
func play(n NoiseMaker) {
	n.MakeSound()
}

一旦你给一个接口类型的变量(或方法的参数)赋值,你就只能调用接口定义的方法。(和C#类似)

只要是满足接口的类型,即使该类型具有其他方法,都可以将该类型赋值给该接口变量。

将指针传递给接口变量

如果一个类型声明了指针接收器方法,就只能将那个类型的指针传递给接口变量。

type Toggleable interface {
	toggle()
}

type Switch string

//需要使用指针类型的接收器
func (s *Switch) toggle() {
	if *s == "on" {
		*s = "off"
	} else {
		*s = "on"
	}
	fmt.Println(*s)
}

调用代码:

var t Toggleable	//定义接口变量
t = &s				//将指针赋值给接口变量
t.toggle()

类型断言

当有一个接口类型的变量时,你能在它之上调用的方法只能是接口定义的。如果你将一个具体类型赋值给接口类型的变量,你能使用类型断言来获得具体类型的值。只有这样你才能调用具体类型定义的(但没有定义在接口中的)方法。

当你将一个具体类型的值赋给一个接口类型的变量时,类型断言让你能取回具体类型。有点类似C#中的类型转换和is关键字。

假如存在如下接口:

type NoiseMaker interface {
	MakeSound()
}

Robat类型实现了该接口:

type Robat string

func (r Robat) MakeSound() {
	fmt.Println("Robat_MakeSound:", r)
}

//定义一个额外的方法
func (r Robat) Walk() {
	fmt.Println("Robat_Walk:", r)
}

将具体值赋值给接口变量:

var noiseMaker NoiseMaker = Robat("FFF")
noiseMaker.MakeSound()

使用类型断言,获取具体类型,并调用接口中没有的方法:

var robot Robat = noiseMaker.(Robat)
robot.Walk()

简单来说,类型断言就像说某物像“我知道这个变量使用接口类型NoiseMaker,但是我很确信这个NoiseMaker实际上是Robot。”

一旦你使用类型断言来取回具体类型的值,你可以调用那个类型上的方法,包括接口中未声明的方法。

类型断言返回第二个bool值来表明断言是否成功。

类型断言失败时,为了避免异常,可以使用断言返回的第二个可选值:

var wy, ok = noiseMaker.(Robat)
if ok {
	wy.Walk()
} else {
	fmt.Println("失败")
}

如果类型断言被用于期待多个返回值的情况,它能有第二个可选的返回值来表明断言是否成功。(并且断言并不会在不成功时出现异常。)第二个值是一个bool,并且当原类型和断言类型相同时,返回true,否则返回false。你可以对于第二个返回值做任何操作,但是按照惯例,它通常被赋给一个名为ok的变量。

recorder, ok := player.(gadget.TapeRecorder)
if ok {
	recorder.Record()
} else {
	fmt.Println("断言失败")
}

error 接口

error类型本质上只是一个接口:

type error interface{
	Error() string
}

因此,如果自定义的类型中,包含一个返回string的Error方法,那么它就满足error接口,并且它是error的值。

自定义error类型:

//定义一个以string为基础类型的类型
type ComedyError string
//满足error接口
func (c ComedyError) Error() string {
	return string(c)
}

//定义一个基础类型为float64的类型
type OverheatError float64
//实现error接口
func (o OverheatError) Error() string {
	return fmt.Sprintf("值:%0.2f", o)
}
//指定函数返回原生error值
func checkTemperature(actual float64, safe float64) error {
	excess := actual - safe
	if excess > 0 {
        //var wyerr error
        //wyerr = OverheatError(excess)
        //return wyerr
		return OverheatError(excess) //返回的是error,实现的具体类型
	}
	return nil
}

func main() {
	var err error //声明一个error类型的变量
	err = ComedyError("这是一个错误信息")	//ComedyError满足error接口,所以可以赋值给接口变量
	fmt.Println(err)

	err = checkTemperature(121.322, 100.0)
	if err != nil {
		log.Fatal(err)
	}
}

error类型像int或者string一样是一个“预定义标识符”,它不属于任何包。它是“全局块”的一部分,这意味着它在任何地方可用,不用考虑当前包信息。

Stringer 接口

fmt包中定义了fmt.Stringer接口:

type Stringer interface{
	String() string
}

因此,任何具有返回string的String()方法的类型都是一个fmt.Stringer。

type CoffeePot string

//满足Stringer接口
func (c CoffeePot) String() string {
	return string(c) + "浪浪浪"
}

许多在fmt包中的函数都会判断传入的参数是否满足stringer接口,如果满足就调用String方法。这些函数包括Print、Println和Printf等。

func main() {
	coffeePot := CoffeePot("wogan")
	fmt.Println(coffeePot.String())
	fmt.Println(coffeePot)
	fmt.Printf("%s", coffeePot)
}

该功能一般应用在格式输出的场景,或自定义格式字符串。

空接口

如果接口声明定义了方法,类型必须实现这个方法才能满足接口。如果定义了一个不需要任何方法的接口,它会被任何类型满足!它被所有的类型满足!

//定义一个空接口
type Anything interface {
}

空接口,可以用来接收任何类型的值。

如果你定义一个接收一个空接口作为参数的函数,你可以传入任何类型的值作为参数。

//定义一个参数为空接口的函数
func wyTest(wy Anything) {
	fmt.Println(wy)
}

func main() {
    //可以为该函数指定任意类型的实参
	wyTest("wwweeeeeeeeeeeee")
	wyTest(1111)
	wyTest(false)
}

除了上述定义空接口的方式外,常用的是直接使用下述形式:

func wyTest2(wy interface{}) {
	fmt.Println(wy)
}

不需要实现任何方法来满足空接口,所以所有的类型都满足它。

使用断言在空接口值上调用函数

如果你有一个接口类型的值,你只能调用接口上的方法。空接口没有任何方法。那意味着你没法调用空接口类型值的任何方法。

为了在空接口类型的值上调用方法,你需要使用类型断言来获得具体类型的值。

//定义一个类型
type SuperWhistle string

//为该类型定义方法
func (w SuperWhistle) Makesound() {
	fmt.Println("Makesound:", w)
}

//将空接口值作为参数
func Acceptanything(thing interface{}) {
	fmt.Println(thing)
	//使用类型断言来获得SuperWhistle
	whistle, ok := thing.(SuperWhistle)
	if ok {
		whistle.Makesound()
	}
}

调用方式:

func main() {
	Acceptanything(3.1415)
    //传入SuperWhistle值
	Acceptanything(SuperWhistle("wwwwww"))
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夕Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值