C# 第五章『面向对象』◆第6节:类的继承

        一、基类和派生类

        在程序设计中实现继承,表示这个类拥有它继承的类的所有公有成员或受保护成员。在面向对象程序设计中,被继承的类称为父类基类(原有的类),实现继承的类成为子类派生类(新生的类)。

        当一个类从另一个类派生出来时,派生类就自然具有基类的数据成员、属性和方法等。在基类中定义的这些成员,已经不需要在派生类定义中重写。在派生类的定义中,只需编写基类所不具备的代码即可。

<访问修饰符>class<父类的名称>
{
    A
    B
}
    class<派生类>:<父类的名称>
{
    A
    B
    C
}

        在C#中适用“:”来标识两个类的继承关系。在继承一个类时,类成员的可访问性是一个重要的问题。派生类(子类)不能访问基类(父类)的私有成员,但是可以访问其公共成员,这就是说,只要使用public声明类成员,就可以让一个类成员被基类(父类)和派生类(子类)同时访问,同时也可以被外部的代码访问。

        另外,为了解决基类(父类)成员的访问问题,C#还提供了另一种可访问性:protected,它表示受保护成员,只有基类(父类)和派生类(子类)才能访问protected成员,外部代码不能访问protected成员。

        备注:

        ①派生类不能继承基类中定义的private成员。

        ②C#中只支持单继承,而不支持多重继承,即在C#中一次只允许继承一个类,不能同时继承多个类。

        ③在实现类的继承中,子类的可访问性一定要低于或等于父类的可访问性。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace _20220805_1
{
    class Goods
    {
        public string TradeCode { get; set; }
        public string FullName { get; set; }
    }
    class JHInfo : Goods
    {
        public string JHID { get; set; }
        public void showInfo()
        {
            Console.WriteLine("进货编号:{0}\n商品编号:{1}\n商品名称:{2}",JHID,TradeCode,FullName);
        }
    }
    class Program
    {

        static void Main(string[] args)
        {
            JHInfo jh = new JHInfo();
            jh.TradeCode = "T101";
            jh.FullName = "笔记本";
            jh.JHID = "JH001";
            jh.showInfo();
            Console.ReadLine();
        }
    }
}

        二、继承的特性

        继承是在类之间建立一种相交的关系,使得新定义的派生类的实例,可以继承已有的基类的特征,并且可以添加新的功能。

        特征:

  1. 派生类只能继承一个基类。所以C#并不支持多重继承,但一个基类可以有多个直接派生类。
  2. 子类应当是对父类的扩展。子类可以添加新的成员,但是不能去去除已经继承的成员的定义。
  3. 继承是可以传递的。例如:c类从b类中派生,而b类又从a类冲派生,那么c类不仅继承了b类的所有成员,同样也继承了a类中的成员。Object作为所有类的父类,我们称之为祖先类。
  4. 构造函数和析构函数不能被继承(需通过base)。除此以外的其他成员,不论对它们定义了怎样的访问方式,都能被继承。父类成员的访问方式只能决定子类是否能访问它们。
  5. 覆盖)子类如果定义了与继承而来的成员同名的新成员,则可以覆盖已继承的成员。但并不意味着删除了继承的成员,只是不能再直接访问这些成员。
  6. 如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有分别声明为virtual和override,派生类方法就会隐藏基类方法。
  7. 类可以定义虚方法、虚属性以及虚索引指示器,它的子类能够重载这些成员,从而实现类的多态性。
//派生类只能继承一个基类,所以C#并不支持多重继承,但一个基类可以有多个直接派生类。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication3
{
    class Clerk
    {
        private string _name;
        public string Name          //定义职员的姓名属性
        {
            get { return _name; }
            set { _name = value; }
        }
        private string _department; //定义职员部分的字段
        public string Department    //定义职员部门的属性
        {
            get { return _department; }
            set { _department = value; }
        }
        public void CSeyHello()
        {
            Console.WriteLine("大家好,我是{0}的{1}", Department, Name);
        }
    }
    class Sales:Clerk                //创建销售类Sales,继承与Clerk
    {
        private int _salesTarget;    //定义职员销售目标的字段
        public int SalesTarget       //定义职员销售目标的属性
        {
            get { return _salesTarget; }
            set { _salesTarget = value; }
        }
        public void SSayHello()
        {
            Console.WriteLine("大家好,我是{0}的{1},我的销售目标是{2}元", Department, Name,SalesTarget);
        }
    }
    //创建技术支持类TechnicalSupport,继承于Clerk
    class TechnicalSupport:Clerk
    {
        private double _satisfactionRate;
        public double SatisfactionRate
        {
            get { return _satisfactionRate; }
            set { _satisfactionRate = value; }
        }
        public void TSSayHello()
        {
            Console.WriteLine("大家好,我是{0}的{1},我的客户打分是{2}分", Department, Name,SatisfactionRate);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Clerk zs = new Clerk();
            zs.Name = "张三";
            zs.Department = "事业部";
            zs.CSeyHello();
            //--------------------------------
            Sales ls = new Sales();
            ls.Name = "李四";
            ls.Department = "销售部";
            ls.SalesTarget = 800000;
            ls.SSayHello();
            //--------------------------------
            TechnicalSupport zh = new TechnicalSupport();
            zh.Name = "王五";
            zh.Department = "售后部";
            zh.SatisfactionRate = 99;
            zh.TSSayHello();
        }
    }
}
//如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有分别声明为virtual和override,派生类方法就会隐藏基类方法。
using System;
namespace Project3
{
    class myClass1
    {
        public void Write()
        {
            Console.WriteLine("这里是基类");
        }
    }
    class myClass2 : myClass1
    {
        public new void Write()   //隐藏方法
        {
            Console.WriteLine("这里是派生类");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            myClass2 b = new myClass2();
            myClass1 a = b;
            a.Write();
            b.Write();
        }
    }
}

        1问:子类继承了父类,那么子类从父类那里继承了什么?

        1答:子类继承了父类的属性和方法,但是子类并没有继承父类的私有字段。

        2问:子类有没有继承父类的构造函数?

        2答:子类并没有继承父类的构造函数,但是子类会默认的调用父类无参数的构造函数,创建父类对象,让子类可以使用父类中的成员。所以,如果在父类中重新写一个有参数的构造函数后,那么那个无参数的构造函数就会被自动去除。子类就调用不到了,所以子类会报错。

        解决方案:在父类中重新写一个无参的构造函数;②在子类中显示的调用父类的构造函数,适用关键字base()。

        三、禁止继承

        有些时候,我们不希望自己编写的类被继承,或者不需要将自己开发的类被派生。在C#语言中,将不能够被继承的类或者不可以作为父类的类,称为封闭类。

        声明封闭类需要在class前面加sealed修饰符,被声明为sealed的类不能够作为父类。一般主要在以下两种情况下适用封闭类。

  1. 当一个类不可能再有子类从这个类派生时,需要将这个类声明为封闭的类。
  2. 如果一个类中所有的方法和属性都被声明为静态(static),则需要将这个类声明为封闭的类。

        四、base关键字

        当子类重写了父类的方法,就无法调用父类的方法了。如果需要在子类的方法中实现原有父类的方法,C#提供了base关键字。

        base关键字用于从派生类中访问基类的成员。base关键字的使用方法与this关键字类似,this关键字代表本类对象,base关键字代表父类对象。

        备注:如果要在子类中适用base关键字调用父类的属性或方法,则父类的属性和方法必须定义为public类型或protected类型。

                   访问父类成员只能在构造函数、实例方法或实例属性中进行,因此,在静态方法中适用base关键字是错误的。

base.property;//调用父类的属性
base.method();//调用父类的方法

        指定创建派生类实例时应调用基类构造函数,用于调用基类的构造函数完成对基类成员的初始化工作。

        在使用base时,要注意base指的是调用“对象”本身,不仅是指基类中看到的变量或方法。

        注意:

        ①通过base不仅可以访问直接基类中定义的域和方法,还可以访问间接基类中定义的域和方法。

        ②在构造方法中调用基类的构造方法时,base()指直接基类的构造方法,而不能指间接基类的构造方法,这是因为构造方法是不能继承的。

        ③由于base指的是对象,所以它不能在static环境中使用,包括静态域、静态方法和static构造方法。

using System;

namespace Demo
{
    class Computer                                   //父类:电脑
    {
        public string sayHello()
        {
            return "欢迎使用";
        }
    }
    class Pad : Computer                               //子类:平板电脑
    {
        public new string sayHello()                  //子类重写父类方法
        {
            return base.sayHello() + "平板电脑";        //调用父类方法,在结果后添加字符串
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Computer pc = new Computer();           //电脑类
            Console.WriteLine(pc.sayHello());
            Pad ipad = new Pad();                   //平板电脑类
            Console.WriteLine(ipad.sayHello());
            Console.ReadLine();
        }
    }
}

        五、继承中的构造函数与析构函数

        在进行类的继承时,派生类的构造函数会隐式调用基类的无参构造函数,但是如果基类也是从其他类派生的,则c#会根据层次结构找到顶层的基类,并调用基类的构造函数,然后在依次调用各级派生类的构造函数。析构函数的执行顺序正好与构造函数相反。

        ①当基类中没有定义构造函数时,派生类会默认地调用基类的默认构造函数。

        ②当基类中编写一个有参构造函数时,再实例化派生类的对象,程序就会调用基类中无参的构造函数,如果该函数存在就调用它,否则编译器会报错。因此,不论程序调用派生类中的哪个构造函数,都是在寻找基类中无参的构造函数,如果没有则报错,而非参数匹配。

        ③基类中编写了构造函数,可以通过base关键字,指定创建派生类实例时应调用基类构造函数。

using System;
namespace Project4
{
    public class myClass1        //基类
    {
        int Number;
        public myClass1()        //构造函数
        {
            Console.WriteLine("派生类调用基类的第一个构造函数");
        }
        public myClass1(int a)
        {
            Number = a;
            Console.WriteLine("派生类调用基类的第二个构造函数");
        }
        public int GetNumber()
        {
            return Number;
        }
    }
    public class myClass2 : myClass1            //派生类
    {
        //这个构造函数调用第一个基类的构造函数
        public myClass2() : base()            //派生类的构造函数
        {
        }
        //这个构造函数调用第二个基类的构造函数
        public myClass2(int a) : base(a)           //派生类的第二个构造函数
        {
        }
        static void Main()
        {
            myClass2 bs1 = new myClass2();
            myClass2 bs2 = new myClass2(1);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_45336030/article/details/126185717