[go学习笔记.第十章.面向对象编程] 9.面向对象的三大特性-继承

本文介绍了Go语言中如何通过结构体嵌入实现类似继承的效果,以提高代码复用性和维护性。通过示例展示了如何创建具有公共属性和方法的学生类,如小学生和大学生,并详细阐述了结构体嵌入的规则,包括字段和方法的访问、匿名结构体的使用以及多重继承的实现。此外,还讨论了如何在创建实例时直接指定匿名结构体字段的值。
摘要由CSDN通过智能技术生成

1.看一个问题,引出继承的必要性

package main

import (
    "fmt"
)

//编写一个学生考试系统
type Pupil struct {
    Name string
    Age int
    Score float64
}
//显示信息
func (p *Pupil) showInfo() {
    fmt.Printf("学生姓名:%v,年龄:%v,成绩:%v\n", p.Name, p.Age, p.Score)
}
//设置成绩
func (p *Pupil) SetScore(s float64) {
    //逻辑判断
    p.Score = s 
}
//考试状态
func (p *Pupil) status() {
    fmt.Println("正在考试")
}

// 大学生,高中生,...
func main() {
    p := &Pupil{
        Name: "Jack",
        Age: 11,
        Score: 110.22,
    }

    p.status()//正在考试
    p.SetScore(22)
    p.showInfo()//学生姓名:Jack,年龄:11,成绩:22
}

对上面代码总结:

1.Pupil和Graduate两个结构体字段和方法几乎一样,有很多相同的代码

2.出现代码的冗余,而且代码不利于维护,同时也不利于功能的扩展

3.解决方法:通过继承方式来解决

2.继承的基本介绍和图示

继承可以解决代码的复用,让编程更加靠近人类思维.

        当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法.

        其他结构体不需要重新定义这些属性(字段)和方法,只需要嵌套一个匿名结构体即可

也就是说:

        在go语言中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体中的字段和方法,从而实现继承特性

 3.嵌套匿名结构体的基本语法

type Goods struct {

        Name string

        Proce int

}

type Book struct {

        Goods         // 这里就是嵌套匿名结构体Goods

        Writer string

}

 4.案例

package main

import (
    "fmt"
)

//因为小学生,大学生,高中生都有共有的属性和方法,故抽出来放到Student结构体中
type Student struct {
    Name string
    Age int
    Score float64
}
func (stu *Student) showInfo() {
    fmt.Printf("学生姓名:%v,年龄:%v,成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
//设置成绩
func (stu *Student) SetScore(s float64) {
    //逻辑判断
    stu.Score = s 
}

//编写一个学生考试系统

// 小学生,大学生,高中生,...
//小学生:
type Pupil struct {
    Student //嵌入了Student匿名的结构体
}
//考试状态:小学生结构体特有的方法,保留
func (p *Pupil) status() {
    fmt.Println("小学生正在考试")
}

//大学生:
type Graduate struct {
    Student //嵌入了Student匿名的结构体
}
//考试状态:大学生结构体特有的方法,保留
func (g *Graduate) status() {
    fmt.Println("大学生正在考试")
}

func main() {
    //当对结构体嵌入了匿名的结构体时,使用的方法会发生变化
    p := &Pupil{}
    p.Student.Name = "jack"
    p.Student.Age = 1
    p.status()//小学生正在考试
    p.Student.SetScore(90.01)
    p.Student.showInfo()//学生姓名:jack,年龄:1,成绩:90.01 

    g := &Graduate{}
    g.Student.Name = "Mary"
    g.Student.Age = 23
    g.status()//大学生正在考试
    g.Student.SetScore(190.01)
    g.Student.showInfo()//学生姓名:Mary,年龄:23,成绩:190.01
}

package main

import (
    "fmt"
)

//因为小学生,大学生,高中生都有共有的属性和方法,故抽出来放到Student结构体中
type Student struct {
    Name string
    Age int
    Score float64
}
func (stu *Student) showInfo() {
    fmt.Printf("学生姓名:%v,年龄:%v,成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
//设置成绩
func (stu *Student) SetScore(s float64) {
    //逻辑判断
    stu.Score = s 
}

//给Student增加一个方法,那么Pupil,Graduate都可以使用这个方法
func (stu *Student) GetSum(n1 int, n2 int) int {
    return n1 + n2
}


//编写一个学生考试系统

// 小学生,大学生,高中生,...
//小学生:
type Pupil struct {
    Student //嵌入了Student匿名的结构体
}
//考试状态:小学生结构体特有的方法,保留
func (p *Pupil) status() {
    fmt.Println("小学生正在考试")
}

//大学生:
type Graduate struct {
    Student //嵌入了Student匿名的结构体
}
//考试状态:大学生结构体特有的方法,保留
func (g *Graduate) status() {
    fmt.Println("大学生正在考试")
}

func main() {
    //当对结构体嵌入了匿名的结构体时,使用的方法会发生变化
    p := &Pupil{}
    p.Student.Name = "jack"
    p.Student.Age = 1
    p.status()//小学生正在考试
    p.Student.SetScore(90.01)
    p.Student.showInfo()//学生姓名:jack,年龄:1,成绩:90.01 
    result := p.Student.GetSum(10, 20)

    fmt.Println(result)
    g := &Graduate{}
    g.Student.Name = "Mary"
    g.Student.Age = 23
    g.status()//大学生正在考试
    g.Student.SetScore(190.01)
    g.Student.showInfo()//学生姓名:Mary,年龄:23,成绩:190.01
    result2 := g.Student.GetSum(110, 20)
    fmt.Println(result2)
}

5.继承给程序带来了便利

(1).代码的复用性提高了

(2).代码的扩展性和维护性提高了

6.继承的深入讨论

(1). 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段,方法都可以使用

package main

import(
    "fmt"
)

type A struct {
    Name string
    age int
}
func (a *A) SayOk() {
    fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
    fmt.Println("A hello", a.Name)
}

type B struct {
    A
}
func main() {
    var b B
    b.A.Name = "Mary"
    b.A.age = 10
    b.A.SayOk()//A SayOk Mary
    b.A.hello()//A hello Mary
}

(2).匿名结构体字段访问可以简化

package main

import(
    "fmt"
)

type A struct {
    Name string
    age int
}
func (a *A) SayOk() {
    fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
    fmt.Println("A hello", a.Name)
}

type B struct {
    A
}
func main() {
    var b B
    b.A.Name = "Mary"
    b.A.age = 10
    b.A.SayOk()//A SayOk Mary
    b.A.hello()//A hello Mary

    //上面的写法可以简化
    b.Name = "Jack"
    b.age = 20
    b.SayOk()//A SayOk Jack
    b.hello()//A hello Jack
}

对上面代码的说明:

1).当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name 
2).编译器会先看 b 对应的类型有没有 Name ,如果有,则直接调用 B 类型的 Name 字段 
3).如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果役有继续查找,如果都找不到就报错

 (3).当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分

package main

import(
    "fmt"
)

type A struct {
    Name string
    age int
}
func (a *A) SayOk() {
    fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
    fmt.Println("A hello", a.Name)
}

type B struct {
    A
    Name string
}

func (b *B) SayOk() {
    fmt.Println("B SayOk", b.Name)
}
func main() {
    var b B
    b.Name = "Linda"    //ok 
    
    b.age = 30  //ok
    b.SayOk()//b SayOk Linda

    b.hello()//A hello
    b.A.Name = "Tom"
    b.hello()//A hello Tom
    b.A.SayOk()//A SayOk Tom
}

(4).结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错

package main

import(
    "fmt"
)
type A struct{
    Name string
    age int
}
type B struct{
    Name string
    score float64
}
type C struct{
    A 
    B 
    //Name string
}

func main()  {
    var c C 
    //如果C没有Name字段,而A,B有Name,这时就必须通过指定匿名结构体名字区分
    //c.Name报错,编译错误,这个规则对方法同样适用
    // c.Name = "tom"   //error
    // c.A.Name = "tom" //ok
    
}

(5).如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

package main

import(
    "fmt"
)
type A struct{
    Name string
    age int
}
//组合结构体
type D struct{
    a A//有名结构体
}
func main()  {
    //如果D中是一个有名结构体,则访问有名结构体的字段时,必须带上有名结构体的名字,如:d.a.Name = "tom"
    var d D 
    // d.Name = "Mary"//error: 结构体D中没有Name,里面有一个有名的结构体A
    d.a.Name = "tom"    //ok
    
}

(6).嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

package main

import(
    "fmt"
)
type A struct{
    Name string
    age int
}
type B struct{
    Name string
    score float64
}
type C struct{
    A 
    B 
    //Name string
}

//组合结构体
type D struct{
    a A//有名结构体
}


type Goods struct{
    Name string
    Price float64
}

type Brand struct{
    Name string
    Address string
}

type TV struct {
    Goods
    Brand
}

type TV2 struct {
    *Goods
    *Brand
}

func main()  {
    // var c C 
    //如果C没有Name字段,而A,B有Name,这时就必须通过指定匿名结构体名字区分
    //c.Name报错,编译错误,这个规则对方法同样适用
    // c.Name = "tom"   //error
    // c.A.Name = "tom" //ok
    
    //如果D中是一个有名结构体,则访问有名结构体的字段时,必须带上有名结构体的名字,如:d.a.Name = "tom"
    var d D 
    // d.Name = "Mary"//error: 结构体D中没有Name,里面有一个有名的结构体A
    d.a.Name = "tom"    //ok

    //嵌套匿名结构体后,也可在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    tv := TV{Goods{"电视剧机", 110.22}, Brand{"华硕","四川"},}
    fmt.Println("tv:", tv)
    tv2 := TV{
        Goods{
            Name : "电视剧机",
            Price : 110.22,
            },
         Brand{
             Name : "华硕",
             Address : "四川",
            },
        }
    fmt.Println("tv2:", tv2)
    tv3 := TV2{&Goods{"电视剧机", 110.22}, &Brand{"华硕","四川"},}
    fmt.Println("tv3:", *tv3.Goods, *tv3.Brand)

    tv4 := TV2{
        &Goods{
            Name : "电视剧机",
            Price : 110.22,
            },
         &Brand{
             Name : "华硕",
             Address : "四川",
            },
        }
    fmt.Println("tv4:" , *tv4.Goods, *tv4.Brand)
}

说明:

1).如果一个结构体有int类型的匿名字段,就不能有第二个

2).如果需要有多个int的字段,则必须给int字段指定名字

package main

import(
    "fmt"
)
type Monster struct{
    Name string
    Age int 
}
type E struct{
    Monster
    int
    n int
}

func main()  {
    //演示匿名字段是基本数据类型
    var e E 
    e.Name = "猪八戒"
    e.Age = 12
    e.int = 20
    e. = 30
    fmt.Println("e:",e)
}

 7.多重继承

一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承
package main

import(
    "fmt"
)
type Goods struct{
    Name string
    Price float64
}
type Brand struct{
    Name string
    Address string
}

type TV struct {
    Goods
    Brand
}

func main()  {
    //嵌套匿名结构体后,也可在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    tv := TV{Goods{"电视剧机", 110.22}, Brand{"华硕","四川"},}
    fmt.Println("tv:", tv)
    tv2 := TV{
        Goods{
            Name : "电视剧机",
            Price : 110.22,
            },
         Brand{
             Name : "华硕",
             Address : "四川",
            },
        }
    fmt.Println("tv2:", tv2)

 多重继承细节说明:

        1).如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分

         2).为了保证代码的简洁性,建议大家尽量不使用多重继承

[上一节][go学习笔记.第十章.面向对象编程] 8.面向对象的三大特性-封装 

[下一节][go学习笔记.第十章.面向对象编程] 10.面向对象的特性-接口 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值