• 面向对象编程三大特征2


    面向对象编程三大特性--继承

    为什么需要继承:

      一个小问题,看个学生考试系统的程序extends01.go,提出代码复用的问题:

    代码:

    package main
    import (
      "fmt"
    )

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

    //小学生
    type Pupil struct {
      Name string
      Age int
      Score int
    }

    //显示他的成绩
    func (p *Pupil) ShowInfo() {
      fmt.Printf("学生名=%v 年龄=%v 成绩=%v ", p.Name, p.Age, p.Score)
    }

    func (p *Pupil) SetScoure(score int) {
      if score < 0 || score > 100 {
        fmt.Println("您输入的成绩不正确")
      }
      p.Score = score
    }

    func (p *Pupil) testing() {
      fmt.Println("小学生正在考试中...")
    }

    //大学生
    type Graduate struct {
      Name string
      Age int
      Score int
    }

    //显示他的成绩
    func (p *Graduate) ShowInfo() {
      fmt.Printf("学生名=%v 年龄=%v 成绩=%v ", p.Name, p.Age, p.Score)
    }

    func (p *Graduate) SetScoure(score int) {
      if score < 0 || score > 100 {
        fmt.Println("您输入的成绩不正确")
      }
      p.Score = score
    }

    func (p *Graduate) testing() {
      fmt.Println("大学生正在考试中...")
    }

    //代码冗余...高中生....

    func main() {

      var pupil = &Pupil{
        Name : "tom",
        Age : 10,
      }
      pupil.testing()
      pupil.SetScoure(90)
      pupil.ShowInfo()

      var graudate = &Graduate{
        Name : "mary",
        Age : 20,
      }
      graudate.testing()
      graudate.SetScoure(80)
      graudate.ShowInfo()

    }

    对上面代码的小结:

    1)Pupil 和 Graduate 两个结构体的字段和方法几乎一样,但是我们却写了两份几乎相同的代码,代码复用性不强。

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

    3)解决方法--通过继承方式来解决

    继承基本介绍和示意图:

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

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

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

    示意图:

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


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

    type Goods struct {
      Name string
      Price int
    }

    type Book struct {
      Goods //这里就是嵌套匿名结构体Goods
      Writer string
    }

    快速入门:

    我们对extends01.go 改进,使用嵌套匿名结构体的方式来实现继承特性,请大家注意体会这样编程的好处

    代码实现:

    package main
    import (
      "fmt"
    )

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

    type Student struct {
      Name string
      Age int
      Score int
    }

    //将Pupil 和 Graduate 共有的方法也绑定到 *Student
    func (stu *Student) ShowInfo() {
      fmt.Printf("学生名=%v 年龄=%v 成绩=%v ", stu.Name, stu.Age, stu.Score)
    }

    func (stu *Student) SetScore(score int) {
      if score < 0 || score > 100 {
        fmt.Println("您输入的成绩不正确")
      }
      stu.Score = score
    }

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

    //小学生
    type Pupil struct {
      Student
    }

    //这是Pupil结构体特有的方法,保留
    func (p *Pupil) testing() {
      fmt.Println("小学生正在考试中...")
    }

    //大学生
    type Graduate struct {
      Student
    }

    //这是Graduate结构体特有的方法,保留
    func (p *Graduate) testing() {
      fmt.Println("大学生正在考试中...")
    }

    func main() {

      //当我们队结构体嵌入了你们结构体后,使用的方法会发生变化。
      pupil := &Pupil{}
      pupil.Student.Name = "tom~"
      pupil.Student.Age = 8
      pupil.testing()
      pupil.Student.SetScore(70)
      pupil.Student.ShowInfo()
      fmt.Println("res=",pupil.Student.GetSum(1,2))

      graduate := &Graduate{}
      graduate.Student.Name = "mary"
      graduate.Student.Age = 20
      graduate.testing()
      graduate.Student.SetScore(90)
      graduate.Student.ShowInfo()
      fmt.Println("res2=", graduate.Student.GetSum(3,4))
    }

    继承的深入讨论:

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

    案例:
    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)
    }

    func (a *A) hello2() {
      fmt.Println("A hello", a.age)
    }

    type B struct {
      A
    }

    func main() {

      var b B
      b.A.Name = "tom"
      b.A.age = 19
      b.A.Sayok()
      b.A.hello()
      b.A.hello2()
    }

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

    案例:
    func main() {

      var b B
      b.A.Name = "tom"
      b.A.age = 19
      b.A.Sayok()
      b.A.hello()
      b.A.hello2()

      //上面的写法可以简化
      b.Name = "smith"
      b.age = 20
      b.Sayok()
      b.hello()
      b.hello2()
    }

    对上面的代码小结:

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

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

    案例:
    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)
    }

    func (a *A) hello2() {
      fmt.Println("A hello", a.age)
    }

    type B struct {
      A
      Name string
    }

    func main() {

      var b B
      b.Name = "jack"
      b.A.Name = "soctt"
      b.age = 100
      b.Sayok()
      b.hello()
    }

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

    案例:

    type A struct {
      Name string
      age int
    }

    type B struct {
      Name string
      score float64
    }

    type C struct {
      A
      B
    }

    func main() {
      var c C
      //如果c 没有Name字段,而A 和 B有Name字段,这时必须指定匿名结构体名字
      //所以 c.Name 就会报编译错误,这个规则对方法也是一样的
      //c.Name = "tom" //error
      c.A.Name = "tom" //OK
      fmt.Println(c) //{{tom 0}{ 0}}
    }


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

    案例:

    type A struct {
      Name string
      age int
    }

    type D struct {
      a A //嵌套了有名结构体
    }

    func main() {
      //如果D 中是一个有名结构体,则访问有名结构体的字段时,必须带上有名结构体的名字
      //比如 d.a.Name
      var d D
      d.a.Name = "jack"
      fmt.Println(d)
    }

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

    案例:

    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() {
      tv1 := TV {Goods{"电视机001", 5000},Brand{"海尔", "山东青岛"},}
      tv2 := TV {
        Goods{"电视机002", 5000.99},
        Brand{"西门子", "北京"},
      }
      tv3 := TV {
        Goods{
          Price : 4000,
          Name : "电视机003",
        },
        Brand{
          Name : "夏普",
          Address : "上海",
        },
      }
      fmt.Println(tv1)
      fmt.Println(tv2)
      fmt.Println(tv3)

      tv4 := TV2 {&Goods{"电视机004", 7000}, &Brand{"创维", "河南"},}
      tv5 := TV2 {
        &Goods{
          Name : "电视机005",
          Price: 6000,
        },
        &Brand{
          Name : "飞利浦",
          Address : "荷兰",
        },
      }
      fmt.Println(*tv4.Goods, *tv4.Brand)
      fmt.Println(*tv5.Goods, *tv5.Brand)
    }

  • 相关阅读:
    【软件构造】Lab1基本流程指导及重难点分析
    【软件构造】关于java中List和Set数据结构不同实现方式和不同遍历方式时间效率的探讨与分析
    程序人生-Hello’s P2P
    WinterCamp2017吃饭睡觉记
    bzoj 3144 [Hnoi2013]切糕
    bzoj 1565 [NOI2009]植物大战僵尸
    bzoj 1061 [Noi2008]志愿者招募
    序列
    Philosopher
    时机成熟之时
  • 原文地址:https://www.cnblogs.com/green-frog-2019/p/11408310.html
Copyright © 2020-2023  润新知