【C#面向对象】第五课——类的继承、调用父类的构造方法、继承的特性

这篇博客介绍了C#面向对象编程中的类继承概念,详细讲解了如何调用父类构造方法,以及继承的特性。通过示例展示了如何在子类中使用base关键字显式调用父类构造方法,强调了继承的单根性和传递性,并探讨了继承在泛型集合应用中的实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

知识点:理解继承的概念 、 掌握在程序中实现继承、 掌握 base 和 protected 关键字 、掌握如何调用父类构造方法 、 掌握继承的特性

 

1、理解继承

          面向对象有三大特性:封装、继承和多态。我们学习过封装,封装是信息隐藏,我们定义类的过程就是对数据的一种封装,今天我们将重点讨论继承。

          继承反映的是类之间的层次关系,生活中就有很多继承的例子,如:我们知道汽车有型号、颜色、排量、油耗等属性,有行驶和刹车方法。而公交车是汽车的一种,除了有汽车基本的属性和方法外,还能载很多人。吊车也是一种汽车,它除了有汽车的特征外,还能吊货。汽车和公交车、吊车之间就是一种继承关系,公交车和吊车继承于汽车。生活中满足“是一种”关系的,基本都属于继承,比如还有:喜剧演员、话剧演员都是一种演员,冰箱、电视、空调都是一种电器,等等。

          如果一个类继承于另外一个类,如:公交车类继承于汽车类,我们把汽车类称为父类或基类,把公交车类称为子类或派生类,有时继承也叫派生,可以说汽车类派生出公交车类。

 

1.1   程序中的继承

          继承能让我们在原有类的基础上进行扩展,派生出新的类,从而最大程度上重用了原有代码。对于类而言,所谓继承就是子类包含父类的数据结构和行为方式,包括字段、属性、方法和事件。尽管在子类本身的定义中没有包含这些定义,但仍然可以使用这些父类成员。在类的继承中,被继承的类叫基类或父类,继承的类叫派生类或子类。子类在继承父类的属性和方法同时,可以定义新的属性和方法。

          观察下面学生类和老师类的类图会发现,它们存在重复的属性:姓名、性别和年龄。如果再定义校长和警察类也可能要定义这些属性,这样就存在大量重复代码。

   

         学生、老师都是一种人,人都有姓名、性别、年龄属性,如果将这些共有属性定义到人类中,学生类和老师类来继承人类,就不用再定义这三个属性了,只需要定义子类自己特定的属性,如分数或科目等。在子类中可以直接访问父类的属性和方法。

 

继承的语法:

class 子类:父类
{
    ...
}

继承的目的:

  • 提高代码的重用性;
  • 提高程序设计的效率;
  • 为程序设计中的特别需要,提供了编写代码的自由空间,从而提高了已有程序设计成果的可扩展性。

 

示例练习1:使用继承

第一步,新建项目:①在VS中新建控制台应用(.NET Framework),这里默认命名为【ConsoleApp5】,单击【确定】按钮,自动完成创建,并打开项目。②在右侧【解决方案资源管理器中】,右键单击项目名称【ConsoleApp5】,依次点击【添加】→【类】,在弹出的【添加新项】窗口中,给类起一个名字Person.cs,然后点击【添加】。

第二步,编写人类:Person类添加成功后,系统会默认打开Person.cs文件,在该文件里声明三个属性

namespace ConsoleApp5
{
    /// <summary>
    /// 人类Person
    /// </summary>
    class Person        //  在人类里,声明三个属性
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Gender { get; set; }
    }
}

第三步,添加学生类:在当前项目中添加一个学生类Student,在系统会默认打开的Student.cs文件中,编写学生类相关信息

代码分析:我们在学生类里,并没有声明Name、Age、Gender属性,但是却可以直接调用,就是因为学生类Student继承了人类Person,因此可以使用父类中的成员。

namespace ConsoleApp5
{
    /// <summary>
    /// 学生类
    /// </summary>
    class Student : Person      //  类名后面加冒号,表示学生类,继承人类
    {
        public float Score { get; set; }        //学生类,在人类的基础上,增加一个分数属性
        
        public void StuSayHello()       //声明一个方法,进行自我介绍
        {
            Console.WriteLine("大家好,我是学生:{0},性别:{1},我今年{2}岁,这次的考试分数是{3}", 
                this.Name, this.Gender, this.Age, this.Score);
        }
    }
}

第四步,添加教师类:在当前项目中添加一个教师类Teacher,在系统会默认打开的Teacher.cs文件中,编写学生类相关信息

namespace ConsoleApp5
{
    /// <summary>
    /// 教师类
    /// </summary>
    class Teacher : Person          //  类名后面加冒号,表示教师类,继承人类
    {
        public string Subject { get;  set; }          //教师类,在人类的基础上,增加一个学科属性
        public void TeacherSayHello()       //声明一个方法,进行自我介绍
        {
            Console.WriteLine("大家好,我是{0}老师,性别:{1},我今年{2}岁,我带的科目是{3}",
                this.Name, this.Gender, this.Age, this.Subject);
        }
    }
}

第五步,在主程序中写代码:在Program.cs文件中的Main方法里,编写代码new一个学生对象,并赋值,调用方法。

代码分析:我们这里省略了初始化教师类,大家练习的时候,可以new一个教师对象,然后初始化,然后调用教师对象的方法。

代码思考:这里在进行对象初始化的时候,代码是不是稍显冗余?如何简化?

    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student();        //声明一个学生对象
            //给对象初始化
            stu.Name = "王迪";
            stu.Gender = "女";
            stu.Age = 18;
            stu.Score = 80;
            stu.StuSayHello();          //调用学生对象的StuSayHello()方法,输出自我介绍
            Console.ReadLine();            
        }
    }

预览效果:

案例思考:是不是父类中的所有成员(属性、方法、事件),子类都可以继承呢?(可以通过访问修饰符,控制属性或方法被调用的位置)

访问修饰符本类中子类中当前项目项目外
private
protected
internal
public

提示:在父类中声明4个属性,分别在父类的方法中、子类的方法中、当前Program类的Main方法中、其他项目里,进行测试。

        private int PrivateInt { get; set; }
        protected int protectedInt { get; set; }
        internal int internalInt { get; set; }
        public  int publicInt { get; set; }

 

1.2  sealed关键字

           继承给程序开发带来了许多好处,但有时候程序员也可能不想让一个类被继承,那么可以使用关键字 sealed 定义类(被 sealed 定义的类叫密封类),如: sealed class A{} ,这代表着A类是密封类,如果再定义 B 类继承 A 类:class B:A ,就会出现编译错误。

举例:

我们将示例1中的人类Person使用sealed关键字,sealed  class Person{}

那么,在想让学生类Student继承人类Person时,会提示编译错误:“Student”无法从密封类型“Person”派生

 

1.3   base关键字

           C#中base关键字在继承中起到非常重要的作用。它与this关键字相比,this关键字代表当前实例。base关键字代表父类,使用base关键字可以调用父类的构造函数、属性和方法。

  • 使用base关键字访问父类成员,语法:base.属性名 = 值;
  • 使用base关键字调用父类构造函数的语法如下:子类构造函数:base(参数列表)
  • 使用base关键字调用父类方法的语法如下:base.父类方法();

 

2、子类调用父类的构造方法

          当我们调用子类构造方法时,系统会首先调用父类的构造方法,然后再调用子类构造方法。 调用的方式有两种:

  •   隐式调用父类构造方法,没有指定调用父类哪个构造方法时,会调用父类无参构造方法。
  •   通过 base 关键字显式调用父类构造方法。

在理解这个内容时,先回顾一下我们前面一课介绍的构造方法的分类及使用

 

2.1  子类隐式调用父类的构造方法

          当创建子类的对象时,系统将会调用父类的构造函数和子类的构造函数,构造函数的执行次序是:先执行父类的构造函数,再执行子类的构造函数

          在示例1中,我们没有添加构造方法,为了查看效果,我们在示例2中,分别给父类Person添加一个无参构造方法和子类Student添加一个无参构造方法。

 

示例练习2:隐式调用父类构造方法

第一步,新建项目:①在VS中新建控制台应用(.NET Framework),这里默认命名为【ConsoleApp5.1】,单击【确定】按钮,自动完成创建,并打开项目。②在右侧【解决方案资源管理器中】,右键单击项目名称【ConsoleApp5.1】,依次点击【添加】→【类】,在弹出的【添加新项】窗口中,给类起一个名字Person.cs,然后点击【添加】。(同示例1的第一步相同)

第二步,编写人类:Person类添加成功后,系统会默认打开Person.cs文件,在该文件里声明三个属性,并声明一个无参构造方法。

    class Person
    {
        public string Name { get; set; }       //  在人类里,声明三个属性:姓名、年龄、性别
        public int Age { get; set; }
        public string Gender { get; set; }
        //声明一个无参构造方法(因为系统默认的无参构造方法没有方法体)
        public Person()
        {
            Console.WriteLine("调用父类的无参构造方法");       //让该方法,在控制台输出一句话
        }
    }

第三步,添加学生类:在当前项目中添加一个学生类Student,在系统会默认打开的Student.cs文件中,编写学生类相关信息(

    /// <summary>
    /// 学生类
    /// </summary>
    class Student : Person      //  类名后面加冒号,表示学生类,继承人类
    {
        public float Score { get; set; }        //学生类,在人类的基础上,增加一个分数属性
        
        public void StuSayHello()       //声明一个方法,进行自我介绍
        {
            Console.WriteLine("大家好,我是学生:{0},性别:{1},我今年{2}岁,这次的考试分数是{3}", 
                this.Name, this.Gender, this.Age, this.Score);            
        }
        public Student()        //声明一个无参构造方法(测试效果使用的)
        {
            Console.WriteLine("调用子类的无参构造方法");        //让该方法,在控制台输出一句话
        }
    }

第四步,在主程序中写代码:在Program.cs文件中的Main方法里,编写代码new一个学生对象,并赋值,调用方法。(这里与示例1第五步步骤相同,我们可以直接复制之前代码)

预览效果:(注意对比一下和示例1的区别,我们可以设置断点,看一下程序运行)

案例思考:示例中,父类只声明了一个无参构造方法,如果父类中同时声明一个有参构造方法,那么子类在调用父类构造方法时,会如何调用?

案例修改:我们在当前项目中,在父类Person里,新声明一个有参方法,打开【Person.cs】文件,编写代码,然后预览效果看一下和刚才的效果有什么区别?思考一下当父类中同时有两个构造方法的时候,默认调用哪一个构造方法?

案例总结:隐式调用父类构造方法,没有指定调用父类哪个构造方法时,会调用父类的无参构造方法。

案例延伸:如果想调用父类的有参构造方法,怎么办?

 

2.1  子类通过base关键字显示调用父类的构造方法

          

示例练习3:显示调用父类构造方法

在示例2的基础上修改

第一步:我们在当前项目中,在子类Student里,新声明一个有参方法,打开【Student.cs】文件,编写代码,

代码分析:注意base关键字的使用。(注意:base()调用父类构造函数时,不需要再次指定参数的类型,因为在子类中已经定义了这些参数,在base()中只需指定变量名即可,参数的类型必须和父类中的一致。)

        //声明一个有参构造方法
        public Student(string name,int age,string gender,float score) : base(name,age,gender)
        {
            this.Score = score;
            Console.WriteLine("调用子类的有参构造方法");
        }

第二步:在Program.cs文件中的Main方法里,新new一个学生对象并赋值,调用方法。

预览效果:

案例总结:

1)base(参数列表)中的参数必须和父类构造方法的参数一致,否则可能出现错误,尝试交换 name 和 age 参数位置,将会提示编译错误:

2)如果不使用base关键字,或者使用base关键字但是不传入参数,那么依然调用父类的无参构造方法。

 

 

3、继承的特性

        C#属于单继承,即派生类最多只能有一个直接基类,但可以传递继承,即A类派生B类,B类派生C类。派生类会保留所有基类以及传递基类的字段、属性、方法等所有内容。如果要在派生类中隐藏基类内容,可以通过new关键字实现,可以通过base来调用基类的内容。继承的特性主要体现在:

  • 单根性:子类只能继承一个父类
  • 传递性:父类能够传给子类,也能够传给"孙子"类(可以查看类图)

 

3.1  继承的单根性

           继承的单根性是指子类只能继承一个父类,不能同时继承多个父类。C#中,派生类只能从一个类中继承。C#不支持多重继承,也就是说,儿子只能有一个亲生父亲,不能同时拥有多个亲生父亲。(C#中使用接口技术实现多重继承。)

           前面的示例中,我们设置学生类Student继承人类Person,如果我们再让学生类Student继承Program类,那么程序提示编译错误。

 

3.2  继承的传递性

           继承的关系可以有很多层,派生类从基类那里继承特性,派生类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。就像现实中爷爷、爸爸和儿子的关系。儿子不仅可以访问父类(爸爸)的所有公有属性方法,也可以访问爸爸的父类(爷爷)的所有公有属性和方法,这种关系称为继承传递性。

示例练习4:继承的传递性

演示内容:动物类会吃东西,人类继承于动物,那么人类也会吃东西,人类还会思考,学生继承人类,学生会吃东西、会思考还会学习。

第一步,新建项目:①在VS中新建控制台应用(.NET Framework),这里默认命名为【ConsoleApp5.2】,单击【确定】按钮,自动完成创建,并打开项目。②在右侧【解决方案资源管理器中】,右键单击项目名称【ConsoleApp5.2】,依次点击【添加】→【类】,在弹出的【添加新项】窗口中,给类起一个名字Animal.cs,然后点击【添加】。(同示例1的第一步相同)

第二步,编写动物类:Person类添加成功后,系统会默认打开Animal.cs文件,在该文件里声明一个方法Eat()。

第三步,添加人类:在当前项目中添加一个人类Person,在系统会默认打开的Person.cs文件中,在该文件里,Person类继承与Animal类,同时在Person类里,声明三个属性,并声明一个无参构造方法、一个有参构造方法、一个普通方法Think()。

    /// <summary>
    /// 人类Person
    /// </summary>
   class Person : Animal         //  类名后面加冒号,表示人类Person,继承动物类Animal 
    {
        public string Name { get; set; }       //  在人类里,声明三个属性:姓名、年龄、性别
        public int Age { get; set; }
        public string Gender { get; set; }
        public Person()
        {
            Console.WriteLine("\n调用人类Person的,无参构造方法");
        }
        public Person(string name,int age,string gender) 
        {
            this.Name = name;
            this.Age = age;
            this.Gender = gender;
            Console.WriteLine("\n调用人类Person的,有参构造方法");
        }
        public void Think()
        {
            Console.WriteLine("\n{0}同学今年{1}岁,性别:{2},在思考,~~~", this.Name,this.Age, this.Gender);
        }
    }

第四步,添加学生类:在当前项目中添加一个学生类Student,在系统会默认打开的Student.cs文件中,在该文件里,Student类继承与Person类,同时在Student类里,声明一个属性,并声明一个无参构造方法、一个有参构造方法、一个普通方法Study()。

    /// <summary>
    /// 学生类
    /// </summary>
    class Student : Person      //  类名后面加冒号,表示学生类Student,继承人类Person
    {
        public float Score { get; set; }        //学生类,在人类的基础上,增加一个分数属性
        public Student()
        {
            Console.WriteLine("调用Student类的,无参构造方法");
        }
        public Student(string name, int age, string gender,float score) : base(name, age, gender)
        {
            this.Score = score;
            Console.WriteLine("调用Student类的,有参构造方法");
        }
        public void Study()       //声明一个方法,进行学习
        {
            Console.WriteLine("{0}同学此次考试{1}分,在学习中……", this.Name, this.Score);           
        }
    }

第五步,在主程序中写代码:在Program.cs文件中的Main方法里,编写代码new一个学生对象,并赋值,调用方法。

        static void Main(string[] args)
        {
            Student jack = new Student("jack", 18,"男",88);      //声明对象的同时,初始化
            jack.Eat();
            jack.Think();
            jack.Study();
            Console.ReadLine();
        }

预览效果:

案例思考:当我们new一个Student对象的时候,会不会调用Animal类的构造方法?

案例延伸:如果要实现以下效果,程序如何修改?

 

 

4、继承与泛型集合的应用

        前面我们学习过泛型集合,知道泛型集合只能保存同一种类型数据,如何在泛型集合中同时保存学生和老师对象呢? 答案是:定义保存它们父类的泛型集合。老师和学生都属于 Person,所以都可以加入到集合中。但是在 访问集合中的对象时,需要对该对象进行类型判断(对象 is 类型),然后在将对象转换为子类类型。

示例练习5:利用继承,在泛型集合中添加不同数据类型

演示内容:动在泛型集合中,同时保存

第一步,新建项目:①在VS中新建控制台应用(.NET Framework),这里默认命名为【ConsoleApp5.3】,单击【确定】按钮,自动完成创建,并打开项目。②在右侧【解决方案资源管理器中】,右键单击项目名称【ConsoleApp5.3】,依次点击【添加】→【类】,在弹出的【添加新项】窗口中,给类起一个名字Person.cs,然后点击【添加】。(同示例1的第一步相同)

第二步,编写人类:Person类添加成功后,系统会默认打开Person.cs文件,在该文件里声明三个属性。

第三步,添加学生类:在当前项目中添加一个学生类Student,在系统会默认打开的Student.cs文件中,编写学生类相关信息

    /// <summary>
    /// 学生类
    /// </summary>
    class Student : Person      //  类名后面加冒号,表示学生类,继承人类
    {
        public float Score { get; set; }        //学生类,在人类的基础上,增加一个分数属性
        public Student(string name, int age, string gender, float score)   : base(name, age, gender)//声明一个有参构造方法,使用base关键字调用父类Person的有参构造方法
        {
            this.Score = score;
            Console.WriteLine("调用子类Student的有参构造方法");
        }
        public Student() { }//声明一个无参构造方法,当我们定义有参构造方法后,系统将不再默认生成无参构造方法
        public void Study()//声明一个方法,输出学习状态
        {
            Console.WriteLine("\n考了{0}分的{1}同学,今年{2}岁,性别:{3},在学习中...",
                this.Score, this.Name,this.Age,this.Gender);
        }
    }

第四步,添加教师类:在当前项目中添加一个教师类Teacher,在系统会默认打开的Teacher.cs文件中,编写学生类相关信息

    /// <summary>
    /// 教师类
    /// </summary>
    class Teacher : Person          //  类名后面加冒号,表示教师类,继承人类
    {
        public string Subject { get;  set; }          //教师类,在人类的基础上,增加一个学科属性
        public void Teach()
        {
            Console.WriteLine("\n教{0}科目的{1}老师,性别:{2},年龄{3}在上课中...", this.Subject,this.Name,this.Gender,this.Age);
        }
        public Teacher(string name, int age, string gender, string subject)//声明一个有参构造方法,没有使用base关键字
        {
            this.Name = name;       //由于没有使用base关键词,因此这里要初始化
            this.Age = age;
            this.Gender = gender;
            this.Subject = subject;
            Console.WriteLine("调用子类Teacher的,有参构造方法");
        }
        public Teacher()
        {
            Console.WriteLine("调用子类Teacher的,无参构造方法");
        }
    }

第五步,在主程序中写代码:在Program.cs文件中的Main方法里,编写代码new一个学生对象和一个教师对象,并初始化,声明一个泛型集合,将学生对象和教师对象添加到集合中,遍历输出集合中的数据

        static void Main(string[] args)
        {
            Student student = new Student("jack", 18, "男", 88);//new一个学生对象,并初始化            
            List<Person> list = new List<Person>();//new一个泛型集合
            list.Add(student);//将学生对象,添加到泛型集合list中
            Console.WriteLine();//空行
            Teacher teacher = new Teacher("jhon", 30, "男", "C#");//new一个教师对象,并初始化
            list.Add(teacher);//将教师对象,添加到泛型集合list中(注意:此时list中,有两种数据类型)
            foreach (Person person in list)//遍历输出泛型集合中的数据
            {
                if (person is Student)//判断数据类型,如果是Student类型
                {
                    ((Student)person).Study();//将数据强制转换为Student类型,并调用Study()方法
                }
                if (person is Teacher)
                {
                    ((Teacher)person).Teach();
                }
            }
            Console.ReadLine();
        }

预览效果:

 

 

本课总结:

  • 继承能让我们在原有类的基础上进行扩展,派生出新的类,从而最大程度上重用了原有代码。 子类在继承父类的属性和方法同时,可以定义新的属性和方法。
  • 继承的语法是:class 子类:父类
  • 在子类中可以使用 base 关键字来访问父类成员。
  • protected 定义的成员,可以在子类中访问,不能被非子类访问。
  • 系统会首先调用父类的构造方法,然后再调用子类构造方法。
  • 可以通过 base 关键字显式调用父类构造方法。
  • 继承的关系可以有很多层,父类的成员可以一层层传递下去。
  • C#中规定一个类只能继承一个父类。
  • 继承给程序开发带来的好处:
    • 继承是面向对象的重要特性,模拟了现实世界的关系,符合人自然的思考方式。
    • 极大提高了代码重用性。
    • 父类和子类有清晰的结构,子类只用关注于实现自身的特性和行为。

 

 

 

 

==========这里是结束分割线=============

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥小丸子

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值