CH07-继承

本章目标

  1. 理解继承的概念
  2. 熟练使用继承建立父类和子类

本章内容

1、理解继承的概念

  1. 为什么要使用继承?

    回顾:在前面章节讲到了PM和SE两大类,将两大类作对比如下 :

    1721719824031

    问题:当程序中又加入了CEO,CTO等角色,出现了相同的属性,如何减少代码冗余,实现代码重用?

    继承可以实现

    具体思路:

    1. 将相同的属性提取到一个父类中
    2. 将SE,PM类继承于父类

    1721720136869

  2. 现实生活中的继承

    1721720234432

    问题:请同学们列出你认为在生活中的继承有那些?

  3. 继承的概念

    1. 什么是继承?

      在C#中,子类继承父类,那么子类就拥有了父类除私有(private)成员外的所有成员。

    2. 简单理解:

      在 C# 中,类可以继承自另一个类

      衍生的类(子类)继承父类的方法和数据成员

      子类继承父类,父类派生子类

      父类又叫基类

      子类又叫派生类

      继承是面向对象的一个重要特性,继承指出两个类符合“A 是 B”的关系(is-a关系)

      例如,哺乳动物 属于(IS-A) 动物,狗 属于(IS-A) 哺乳动物,因此狗 属于(IS-A) 动物。

      例如:SE is a Employee

    3. 语法:

      1
      2
      3
      4
      5
      6
      7
      8
      <访问修饰符> class <基类>
      {
      ...
      }
      class <派生类> : <基类>
      {
      ...
      }
    4. 继承关系的类图,如下表示:

      1721720972369

    5. 演示案例,通过继承实现PM和SE的自我介绍:

      1721720136869

      1. 创建父类

        提取公共成员

        1
        2
        3
        4
        5
        6
        7
        public class Employee
        {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Gender { get; set; }
        }
      2. 创建子类,添加修改成员

        1. 添加SE类:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          public class SE : Employee
          {
          public SE(int id, string name, int age, string gender, int popularity)
          {
          this.Id = id;
          this.Name = name;
          this.Age = age;
          this.Gender = gender;
          this.Popularity = popularity;
          }
          //定义自己的属性
          public int Popularity { get; set; }

          public void SayHi()
          {
          Console.WriteLine("大家好,我叫:{0},工号:{1},年龄:{2},人气值:{3}", base.Name, base.Id, base.Age, this.Popularity);
          }
          }
        2. 添加PM类:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
           public class PM : Employee
          {
          public PM(int id, string name, int age, string gender, int yearOfExperience)
          {
          this.Id = id;
          this.Name = name;
          this.Age = age;
          this.Gender = gender;
          this.YearOfExperience = yearOfExperience;
          }
          //定义自己的属性
          public int YearOfExperience { get; set; }

          public void SayHi()
          {
          Console.WriteLine("大家好,我叫:{0},工号:{1},年龄:{2},项目管理经验:{3}", base.Name, base.Id, base.Age, this.YearOfExperience);
          }
          }public class PM : Employee
          {
          //定义自己的属性
          public int YearOfExperience { get; set; }

          public void SayHi()
          {
          Console.WriteLine("大家好,我叫:{0},工号:{1},年龄:{2},项目经验:{3}年", base.Name, base.Id, base.Age, this.YearOfExperience);
          }
          }
        3. 测试类:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          static void Main(string[] args)
          {
          //实例化一个程序员对象
          SE engineer = new SE(112, "艾边成", 25, "男", 100);
          engineer.SayHi();
          //实例化一个PM对象
          PM pm = new PM(890, "盖茨", 50, "男", 5);
          pm.SayHi();

          Console.ReadKey();
          }

          注意事项:

          1. base是调用父类的成员

            C# 中还有一个关键字base,它表示父类,可以用于访问父类的成员。例如:调用父类的属性、调用父类的方法、调用父类的构造函数。

          2. this是调用本类的成员

2、protected访问修饰符

  1. 回顾访问修饰符:

    1. public:子类继承父类,可以访问父类成员,同时任何类都可以任意访问
    2. private:子类无法访问
  2. 问题:如何让父类中的某个成员只允许其子类访问?

    protected访问修饰符,只允许继承它的子类访问

    三大访问修饰符对比如下:

    类内部 子类 其他类
    public 可以 可以 可以
    private 可以 不可以 不可以
    protected 可以 可以 不可以
  3. 三种访问修饰符对类成员的访问限制强度:private > protected > public

  4. 演示案例,将Employee的属性修改为protected

    1. 父类添加protected修饰符

      1
      2
      3
      4
      5
      6
      7
      8
      public class Employee
      {
      public int Id { get; set; }
      protected string Name { get; set; }//只能给本类或子类使用
      protected int Age { get; set; }//只能给本类或子类使用
      public string Gender { get; set; }

      }
    2. 子类可以正常调用

    3. 在测试类创建父类对象,使用Name和Age,会报错,不允许在其他类中调用

3、继承关系中的构造函数

  1. 问题:构造子类对象,父类做了什么?

    1
    2
    3
    4
    5
    6
    7
    8
    public SE(int id, string name, int age, string gender, int popularity)
    {
    this.Id = id;
    this.Name = name;
    this.Age = age;
    this.Gender = gender;
    this.Popularity = popularity;
    }

    当在子类中写入构造函数,那么子类构造函数默认调用了父类的隐式构造函数,通过base调用构造函数,相当于如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    public SE(int id, string name, int age, string gender, int popularity):base()
    {
    this.Id = id;
    this.Name = name;
    this.Age = age;
    this.Gender = gender;
    this.Popularity = popularity;
    }

    说明:

    base关键字:显式调用父类构造函数

  2. 父类实现带参构造函数,让代码更简洁:

    1. 给父类添加构造函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class Employee
      {
      public Employee(int id, string name, int age, string gender)
      {
      this.Id = id;
      this.Name = name;
      this.Age = age;
      this.Gender = gender;
      }
      public int Id { get; set; }
      protected string Name { get; set; }
      protected int Age { get; set; }
      public string Gender { get; set; }

      }
    2. 优化子类代码,调用父类构造函数

      1. SE类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        public class SE : Employee
        {
        public SE(int id, string name, int age, string gender, int popularity) : base(id, name, age, gender)
        {
        this.Popularity = popularity;//只给本身属性赋初始值
        }
        //定义自己的属性
        public int Popularity { get; set; }

        public void SayHi()
        {
        Console.WriteLine("大家好,我叫:{0},工号:{1},年龄:{2},人气值:{3}", base.Name, base.Id, base.Age, this.Popularity);
        }
        }
      2. PM类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        public class PM : Employee
        {
        public PM(int id, string name, int age, string gender, int yearOfExperience) : base(id, name, age, gender)
        {
        this.YearOfExperience = yearOfExperience;
        }
        //定义自己的属性
        public int YearOfExperience { get; set; }

        public void SayHi()
        {
        Console.WriteLine("大家好,我叫:{0},工号:{1},年龄:{2},项目管理经验:{3}", base.Name, base.Id, base.Age, this.YearOfExperience);
        }
        }
    3. 注意事项:

      1. 在子类构造函数中通过base来调用构造函数,如上述代码演示;

      2. 调用父类构造函数传递参数,不需再次指定参数的数据类型

        1
        2
        3
        4
        5
        public PM(string id,string name,int age, Gender gender, int yearOfExperience): base(string id,int age,  string name,Gender gender)//编译出错,base()不能指定数据类型
        {
        this.YearOfExperience = yearOfExperience;
        }

      3. 调用父类构造函数传递参数,参数的变量名必须与父类构造函数中的参数名一致

        1
        2
        3
        4
        5
        public PM(string id,string name,int age, Gender gender, int yearOfExperience):       base(id,nianLing, xingMing, gender)//编译出错,参数名和子类构造函数参数名不一致
        {
        this.YearOfExperience = yearOfExperience;
        }

      4. 如果父类没有无参的构造函数子类构造函数必须指明调用父类哪个构造函数

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        class Person//父类未定义无参构造函数
        {
        public Person(int age, string name)
        {
        this.Age = age;
        this.Name = name;
        }
        //…省略属性代码…
        }
        class Student:Person
        {
        public Student(int age, string name, string hobby)//必须指定调用父类的构造函数
        {
        this.Age = age;
        this.Name = name;
        this.Hobby = hobby;
        }
        }

4、继承的特征

  1. 继承的二大特征:

    1. 传递性
    2. 单根性
  2. 第一大特征:传递性

    1721725210249

    1721725286121

    演示案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Truck : Vehicle{}
    public class SmallTruck : Truck
    {
    public void SmallTruckRun()
    {
    Console.WriteLine("微型卡车在行驶!");
    }
    static void Main(string[] args)
    {
    SmallTruck smalltruck = new SmallTruck();
    smalltruck.VehicleRun();//调用父类的父类成员
    smalltruck.TruckRun();//调用父类的成员
    smalltruck.SmallTruckRun();//调用自己的成员
    }
    }

  3. 第二大特征:单根性

    1. 问题:某类人(CharmingPerson)既有软件工程师(SE)的天赋,也有音乐家(Musician)的气质。是否可以用以下代码来描述?

      1721725507582

      代码如下:

      1
      public class CharmingPerson : SE, Musician{}//错误代码
    2. 说明:C#中子类不能继承多个父类,一个子类只能继承一个父类

5、IS-A的运用

  1. 案例:实现多个员工逐个问好(包括SE和PM)

    1. 分析:子类 is a 父类,子类可以加入父类类型的泛型集合List。即SE或PM对象都可以加入集合empls中

    2. 参考代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      static void Main(string[] args)
      {
      //实例化一个程序员对象
      SE engineer1 = new SE(112, "艾边成", 25, "男", 100);
      SE engineer2 = new SE(113, "艾游戏", 22, "男", 98);
      SE engineer3 = new SE(114, "张小利", 26, "女", 100);
      PM pm = new PM(890, "盖茨", 50, "男", 5);

      //将员工添加到集合中
      List<Employee> list = new List<Employee>() { engineer1, engineer2, engineer3, pm };

      foreach (Employee employee in list)
      {
      if(employee is SE)
      {
      SE se = (SE)employee;//将父类转换成子类
      se.SayHi();
      }
      if (employee is PM)
      {
      PM p = (PM)employee;//将父类转换成子类
      p.SayHi();
      }
      }
      Console.ReadKey();
      }
    3. 演示结果:

      1721726132407

本章总结

1721726202773

本章作业

  1. 模拟汽车行驶

    1. 需求说明:

      1. 编写控制台程序实现汽车与卡车的继承
        1. 汽车类Vehicle有VehicleRun()方法, 输出“汽车在行驶!”
        2. 卡车类Truck有TruckRun()方法,输出“型号为XX、产地为XX的卡车在行驶!”
        3. 实例化一个卡车调用汽车和卡车的方法
    2. 实现思路:

      1. 创建Vehicle类,并添加属性“类型”和“产地”以及构造函数
      2. 给Vehicle类添加方法VehicleRun()
      3. 创建Truck类继承Vehicle,添加构造函数
      4. 给Truck类添加方法TruckRun()
      5. 编写测试方法
    3. 效果图如下:

      1721726313582

  2. 实现工作汇报

    1. 需求说明:

      1. PM类和SE类均继承Employee,公共属性在父类构造函数中初始化
      2. 实现不同员工汇报工作的DoWork()方法
        1. SE通过遍历工作项,输出工作信息
        2. PM输出固定工作信息
    2. 实现思路:

      1. Job类定义工作项
      2. SE和PM继承员工类
      3. DoWork() 是子类 特有的方法
    3. 类图如下:

      1721726641368

    4. 实现效果图:

      1721726666016