在上一节 golang中接口的面向对象(一)–多态特征中,我们学习了galang中的多态,
那么这一节中,我们来学习继承。
一般我们可以使用匿名字段嵌入结构体中来实现继承;
type geometry interface {
sayHi()
}
type rect struct {
geometry //这里使用匿名字段,使得rect 继承了 geometry 类型
len, wid float32
}
或者匿名指针(接口类型是一种抽象类型,不能被实例化,所以 *geometry 类型是不存在的)
type geometry interface {
sayHi()
}
type rect struct {
geometry //这里使用匿名字段,使得rect 继承了 geometry 类型
len, wid float32 //如果匿名字段是接口类型,则不能使用 *geometry
}
type circle struct {
*rect //这里使用匿名字段,使得circle 继承了 rect 类型
radius float32
}
不论嵌入 rect 还是 rect 都是一样的,因为rect是可以被实例化的,所以rect是存在的,
在继承中,使用rect 还是 *rect 都是一样的。
我们继续用上一节中的例子,我们修改上一节中的示例代码,如下所示:
type geometry interface {
sayHi() //geometry 只有一个方法sayHi()
}
type rect struct {
geometry //这里使用匿名字段,使得rect 继承了 geometry 类型
len, wid float32
}
func (r rect) sayHi() { //这里rect 作为继承者,重写了sayHi方法
fmt.Println("i am a rect")
}
func (r rect) area() float32 { //这里rect 作为geometry 的超集,增加了area方法
return r.len * r.wid
}
func (r rect) perim() float32 { //这里rect 作为geometry 的超集,增加了perim方法
return 2 * (r.len + r.wid)
}
type circle struct {
rect //这里使用匿名字段,使得circle 继承了 rect 类型
radius float32
}
func show(name string, param interface{}) {
switch param.(type) {
case circle:
fmt.Printf("[c]area of %v is %v \n", name, param.(circle).area())
fmt.Printf("[c]perim of %v is %v \n", name, param.(circle).perim())
case rect:
fmt.Printf("[r]area of %v is %v \n", name, param.(rect).area())
fmt.Printf("[r]perim of %v is %v \n", name, param.(rect).perim())
default:
fmt.Println("wrong type!")
}
}
func main() {
rec := rect{
len: 1,
wid: 2,
}
show("rect", rec)
cir := circle{
radius: 1,
}
show("circle", cir)
}
输出:
i am a rect
[r]area of rect is 2
[r]perim of rect is 6
i am a rect
[c]area of circle is 0
[c]perim of circle is 0
circle 继承了 rect 类型的属性以及方法,因为circle类型长宽属性均为0,所以area 、perim 计算结果都是0
如果我们在rect类型对sayHi方法重写之前,加上下面这段代码,大家考虑一下会出现什么结果?
func (r geometry) sayHi() {
fmt.Println("i am a geometry")
}
编译器会报invalid receiver type geometry (geometry is an interface type)这样的错误,
说明interface 类型不能作为方法的接收者,此刻它内部的方法是并未实现的,
从上面的例子,我们可以看到rect类型继承了geometry 类型的sayHi方法,并在之后重写了该方法,并且rect还增加了两个新的方法area以及perim。
而circle类型则继承了rect类型的所有属性以及方法(仅仅作为一个例子,其实circle是不可能存在长、宽属性的)。
我们来修改一下上面的代码,show函数main函数保持不变,
type geometry interface {
sayHi()
}
type rect struct {
len, wid float32
}
func (r rect) sayHi() {
fmt.Println("i am a rect")
}
func (r rect) area() float32 {
return r.len * r.wid
}
func (r rect) perim() float32 {
return 2 * (r.len + r.wid)
}
type circle struct {
rect //这里circle 继承了 rect的属性len, wid 以及方法sayHi、area、perim
radius float32
}
func (c circle) area() float32 { //这里circle 重写了 area方法
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float32 { //这里circle 重写了 perim方法
return 2 * math.Pi * c.radius
}
注意:上述代码中circle 并没有重写sayHi方法。
输出如下:
i am a rect
[r]area of rect is 2
[r]perim of rect is 6
i am a rect
[c]area of circle is 3.1415927 // 重写了 area方法,计算结果改变
[c]perim of circle is 6.2831855 // 重写了 perim方法,计算结果改变
我们修改代码,show函数main函数保持不变,为circle 增加对sayHi方法的重写,
type circle struct {
rect
radius float32
}
func (r circle) sayHi() { //这里circle 重写了 sayHi方法
fmt.Println("i am a circle")
}
func (c circle) area() float32 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float32 {
return 2 * math.Pi * c.radius
}
输出:
i am a rect
[r]area of rect is 2
[r]perim of rect is 6
i am a circle // 重写了 sayHi方法,打印发生改变
[c]area of circle is 3.1415927
[c]perim of circle is 6.2831855
类型的变换
我们再一次修改代码,只修改main函数,其它代码不变,
func main() {
var ige geometry //接口类型
var irec rect
var irec rect
irec.len = 1
irec.wid = 2
var icir circle
icir.len = 0 //匿名字段的访问,有一点特别,以后的文章中我们再来学习
icir.wid = 0 //匿名字段的访问,有一点特别
icir.radius = 1
fmt.Printf("ige addr is %p\n", &ige)
fmt.Printf("irec addr is %p\n", &irec)
fmt.Printf("icir addr is %p\n", &icir)
//ige.sayHi() //报错,ige是一个接口类型,其本身并未实现sayHi方法
ige = irec // rect转换为geometry类型,看起来是不是怀疑是一个指针传递?稍后我们再看
fmt.Printf("ige addr is %p\n", &ige)
ige.sayHi()
//ige.len = 1 //报错ige.len undefined (type geometry has no field or method len)
show("rect", ige)
ige = icir //circle转换为geometry 类型
fmt.Printf("ige addr is %p\n", &ige)
ige.sayHi()
//ige.wid= 4 //报错
//ige.radius= 4 //报错
show("circle", ige)
//irec = icir //报错,无法进行类型转换
//icir = irec //报错,无法进行类型转换
}
输出:
ige addr is 0xc000088240
irec addr is 0xc0000a20b0
icir addr is 0xc0000a20c0
ige addr is 0xc000088240
i am a rect
[r]area of rect is 2
[r]perim of rect is 6
ige addr is c000088240
i am a circle
[c]area of circle is 3.1415927
[c]perim of circle is 6.2831855
从上面代码可以看出,对ige = icir 赋值的操作,是一个值传递。
我们可以看到从一个超集(rect类型)赋值到 子集(geometry 类型),并没有发生内存溢出,
大家可以思考一下,这是为什么?