一、什么是多态
#接口多态分为两种
1、 多态参数 #我们前面定义一个接口并实例化变量之后
#这个接口变量就可以接收任何实现了该接口的类型
2、 多态数组 #多态数组和参数的区别在于,他定义了一个数组
#这个数组的类型就是接口,他用于存其他实现了这个接口的结构体实例化的变量
案例
package main
import "fmt"
type Usb interface {
Start()
Stop()
}
type Phone struct{}
func (p Phone) Start(){fmt.Println("手机开始工作")}
func (p Phone) Stop(){fmt.Println("手机停止工作")}
type Camera struct{}
func (c Camera) Start() {fmt.Println("相机开始工作")}
func (c Camera) Stop() {fmt.Println("相机停止工作")}
func main(){
var usbArr [3]Usb //这里定义一个Usb接口类型的数组
fmt.Println(usbArr)
usbArr[0] = Phone{} //Phone和Camera结构体已经实现了接口
usbArr[1] = Phone{} //将实例化写入到这个接口宿主中使用
usbArr[2] = Camera{}
fmt.Println(usbArr)
}
返回
[<nil> <nil> <nil>] //索引位 0、1、2
[{} {} {}] //结构体操作 start stop,这里没给动作所以为空
小结
前面我们已知数组是只能存放相同数据类型的值
这里我们将不同的结构体都存放到数组的接口中,这就是多态数组
二、类型断言
我们接口定义了一些方法的规范,实现了该接口方法的结构体,我们可以通过接口调用
但是,如果我们在结构体里面添加了接口没定义的方法,那么接口该怎么调用呢
看一段代码
package main
import "fmt"
type Point struct{
x int
y int
}
func main(){
var a interface{}
var point Point = Point{1,2}
a = point
var b Point
b = a
fmt.Println(b)
}
返回
# command-line-arguments
.\main.go:16:4: cannot use a (type interface {}) as type Point in assignment: need type assertion
上面的代码中a = point 将结构体变量赋值给了空接口,我们前面知道空接口是可以接收
任意类型的值的,但是他下面将b = a,又将接口类型的值重新赋值给Point类型是不可用的
不能使用空接口类型进行赋值,如果想要从接口类型转回来需要类型断言操作
案例
package main
import "fmt"
type Point struct{
x int
y int
}
func main(){
var a interface{}
var point Point = Point{1,2}
a = point
var b Point
b = a.(Point) //类型断言
fmt.Println(b) //{1 2}
}
1、类型断言是怎么实现的
a.(Point) 类型断言,他会判断a变量是否指向了Point类型的变量
如果是就转换成Point类型,并赋值给b变量,否则报错
(如果想将空接口转换为特定的结构体,就可以使用类型断言)
错误案例
package main
import "fmt"
func main(){
var t float32
var x interface{}
x = t
y := x.(float64) //原先t是float32类型,我们这里故意转换为float64类型
fmt.Println(y)
}
返回
panic: interface conversion: interface {} is float32, not float64
在进行类型断言时,如果类型不匹配,就会报panic
因此进行类型断言时,要确保原来的空接口指向的就是要断言的类型
我们为了防止panic的出现,这里做一个检测机制
package main
import "fmt"
func main(){
var t float32
var x interface{}
x = t
y,err := x.(float64) //在断言时传入的只有两个
//一个是具体的数值,一个是状态值err 通过变量接收
if err { //判断err 是否为true,判断上面转换是否成功
fmt.Println("转换成功")
} else{
fmt.Println("转换失败")
}
fmt.Println("继续执行",y)
}
在进行断言时,带上检测机制,如果成功就OK,失败了也不要报panic
panic会使得程序崩溃,后续代码无法执行
另一种写法
package main
import "fmt"
func main(){
var x interface{}
var t float32
x = t
//直接把类型断言做成判断
if y,err := x.(float64); err {
fmt.Println("转换成功")
fmt.Println(y)
} else{
fmt.Println("转换失败")
}
fmt.Println("继续执行")
}
类型断言最佳实践1
在前面的Usb接口案例做改进,给Phone结构体添加一个特有的方法call
当Usb接口接收的是Phone结构体变量时,还需要调用call方法
package main
import "fmt"
type Usb interface {
Start()
Stop()
}
type Phone struct{name string}
func (p Phone) Start(){fmt.Println("手机开始工作")}
func (p Phone) Stop(){fmt.Println("手机停止工作")}
type Camera struct{}
func (c Camera) Start() {fmt.Println("相机开始工作")}
func (c Camera) Stop() {fmt.Println("相机停止工作")}
type Computer struct{}
func (computer Computer) Working(usb Usb) {
usb.Start()
usb.Stop()
}
func main(){
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Phone{"尼康"}
var computer Computer
for _,value := range usbArr {
computer.Working(value)
}
}
我们要做的是,当调用Working方法时,如果发现传入到接口中的是Phone结构体变量时
我们会单独调用一下Phone中特有的Call方法(我们自己创建)
但是我们无法直接去指定usb.Call 因为接口中没有指定这个规范
我们可以依照类型断言去判断接口中的值,是否是Phone类型
如果是则调用Phone下的call方法
package main
import "fmt"
type Usb interface {
Start()
Stop()
}
type Phone struct{name string}
func (p Phone) Start(){fmt.Println("手机开始工作")}
func (p Phone) Stop(){fmt.Println("手机停止工作")}
//给Phone添加一个Call方法,这个方法接口是没有的,是Phone特有的方法
func (p Phone) Call(){fmt.Println("手机炸了")}
type Camera struct{}
func (c Camera) Start() {fmt.Println("相机开始工作")}
func (c Camera) Stop() {fmt.Println("相机停止工作")}
type Computer struct{
}
func (computer Computer) Working(usb Usb) {
usb.Start()
if phone,err := usb.(Phone); err == true { //类型断言,怕判断是否是Phone结构体类型
phone.Call()
}
usb.Stop()
}
func main(){
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Phone{"尼康"}
//Phone还有一个特有的方法call,请遍历Usb数组,如果是Phone变量
//除了调用Usb接口声明的方法外,还需要调用Phone特有的方法call
var computer Computer
for _,value := range usbArr {
computer.Working(value)
}
}
类型断言最佳实践2
写一个函数,循环判断传入参数的类型
package main
import "fmt"
func TypeJudge(items... interface{}){
//items... 可变参数 可以接收多个值放在items中
//这里是空接口,可以接收任意类型的实参
for index,x := range items{
switch x.(type) {
case bool:
fmt.Printf("第%v个参数是bool类型, 值是%v\n",index,x)
case float32:
fmt.Printf("第%v个参数是float32类型, 值是%v\n",index,x)
case float64:
fmt.Printf("第%v个参数是float64类型, 值是%v\n",index,x)
case int,int32,int64:
fmt.Printf("第%v个参数是整数类型, 值是%v\n",index,x)
case string:
fmt.Printf("第%v个参数是string类型, 值是%v\n",index,x)
default:
fmt.Printf("第%v个参数是 不确定类型, 值是%v\n",index,x)
}
}
}
func main(){
var n1 float32 = 1.1
var n2 float64 = 2.2
var n3 int32 = 30
var name string = "tom"
address := "北京"
n4 := 300
//上面设置多个字符
TypeJudge(n1,n2,n3,name,address,n4)
}
返回
第0个参数是float32类型, 值是1.1
第1个参数是float64类型, 值是2.2
第2个参数是整数类型, 值是30
第3个参数是string类型, 值是tom
第4个参数是string类型, 值是北京
第5个参数是整数类型, 值是300
类型断言最佳实践3
在上面的案例的基础上添加了判断是否是结构体
package main
import "fmt"
//添加一个结构体
type Student struct{}
func TypeJudge(items... interface{}){
for index,x := range items{
switch x.(type) {
case bool:
fmt.Printf("第%v个参数是bool类型, 值是%v\n",index,x)
case float32:
fmt.Printf("第%v个参数是float32类型, 值是%v\n",index,x)
case float64:
fmt.Printf("第%v个参数是float64类型, 值是%v\n",index,x)
case int,int32,int64:
fmt.Printf("第%v个参数是整数类型, 值是%v\n",index,x)
case string:
fmt.Printf("第%v个参数是string类型, 值是%v\n",index,x)
//在case中添加对比Student类型即可
case Student:
fmt.Printf("第%v个参数是Student类型, 值是%v\n",index,x)
case *Student:
fmt.Printf("第%v个参数是*Student类型, 值是%v\n",index,x)
default:
fmt.Printf("第%v个参数是 不确定类型, 值是%v\n",index,x)
}
}
}
func main(){
var n1 float32 = 1.1
var n2 float64 = 2.2
var n3 int32 = 30
var name string = "tom"
address := "北京"
n4 := 300
//声明结构体
stu1 := Student{}
stu2 := &Student{}
//添加stu变量
TypeJudge(n1,n2,n3,name,address,n4,stu1,stu2)
}