继承

 

            Person zsPerson = new Chinese();

 

 

继承

前言:在本篇的博客中,我们会讲解继承的类型、实现继承、访问修饰符、接口。继承时我们本次的主题,我们将讨论c#和.Net Framwork如何处理继承。

一:继承的类型:

1.1:实现继承和接口继承:

在面向对象的编程中,有两种截然不同的继承类型:实现继承和接口继承。

实现继承:表示一个类型派生于基类型。它拥有该基类型的所有成员字段和函数。在实现继承中,派生类型采用基类型中每个函数的实现代码,除非在派生类型的定义中指定重写某个函数的实现代码。在需要给现有的类型添加功能。或许多相关的类型共享一组重要的公共功能时,这种类型的继承非常有用。

接口继承:表示一个类型只继承了函数的签名。没有继承任何实现代码。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。 

1.2:多重继承:

c#不支持多重实现继承。而c#又允许类型派生自多个接口——多重接口继承。这说明,c#类可以派生自另一个类和任意多个接口。更准确的说:因为System.Object是一个公共的基类,所以每个c#(除了Object类之外)都有一个基类,还可以有任意多个基接口。 

1.3:结构和类:

使用结构的一个限制是结构不支持继承,但每个结构都自动的派生于System.ValueType。不能编码实现类型层次的结构。但接口可以实现接口。换言之:结构并不支持继承,但是支持接口继承。 

结构总是派生自System.ValueType,他们还可以派生自任意多个接口。

类总是派生于System.Object或用户选择的另一个类,他们还可以派生自任意多个接口。

二:实现继承:

如果要声明自另一个类的一个类,就可以使用下面的语法:

 

    public class Chinese : Person
    {
        
    }

 


如果类(或结构)也派生自接口,则用逗号分隔列表中的基类和接口:

    public class Chinese : Person, IInterface1, IInterface2
    {

    }

如果在类定义中没有指定基类,c#编译器就假定System.Object是基类。因此下面的两段代码生成相同的结果:

 

    public class Chinese : object
    {

    }

 

    public class Chinese
    {

    }

如果要引用Object类,就可以使用object关键字,智能编译器会识别它,因此便于编辑代码。

2.1:虚方法:把一个基类函数声明为virtual,就可以在任何派生类中重写该函数:

 

        public virtual string VirtualMathod()
        {
            return "The method is virtual and defined in MyBaseClass";
        }

 


c#中虚函数的概念与标准OOP的概念相同:可以在派生类中重写虚函数。在调用方法时,会调用该类对象的合适方法。在c#中,函数在默认的情况下不是虚拟的,但(除了构造函数以外)可以显示的声明virtual。除非显示指定,否则函数就不是虚拟的。因此在c#要求在派生类的函数重写另一个函数时,要使用override关键字显示的声明:

 

        public override string VirtualMathod()
        {
            return "This method is an override defined in MyDerivedClass";
        }


成员字段和静态函数都不能声明为virtual,因为这个概念只对类中的实例函数成员有意义。 

 

三:抽象类和抽象函数:

c#允许把类和函数声明为abstract。抽象类不能被实例化。而抽象函数不能直接实现。必须在非抽象的派生类中重写。显然,抽象函数本身也是虚拟的(尽管也不需要提供virtual关键字,实际上,如果提供关键字会产生一个编译的错误)。如果类包含抽象函数,则类也应该是抽象的。

 

        public abstract string VirtualMathod();//abstract method

 

四:密封类和密封方法:

c#允许把类和方法声明为sealed。对于类,这表示不能继承该类,对于方法,这表示不能重写该方法。 

要在方法或者属性上使用sealed关键字,必须先从基类上把它声明为要重写的方法或者属性。如果基类上不希望有重写的方法或者属性,就不要把它声明为virtual。

五:派生类的构造函数:

在创建派生了类的实例的时候,实际上会有多个构造函数起作用。要实例化的类的构造函数本身不能初始化类,还必须调用基类中的构造函数。

    public abstract class Person
    {

        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

    }

 

    public class Chinese
    {
        private int _age;
    }

客户端的调用:

            Person zsPerson = new Chinese();

显然,成员字段name和age都必须在实例zsPerson时进行初始化。如果没有提供自己的构造函数,而是仅依赖于默认的构造函数,那么name会初始化为null引用。age初始化为0.这就有关于我们构造函数的执行的顺序了。

我们首先来理解一下构造函数的执行的顺序:假定默认的构造函数一直在使用:编译器首先找到视图实例化的类的构造函数,在本类中是Chinese,这个默认的Chinese构造函数为其直接基类Person运行默认的构造函数。然后Person构造函数为其直接基类System.Object运行默认的构造函数。System.Object没有任何的基类,所有他的构造函数执行。并把控制权返给Perso构造函数,现在执行的是Person构造函数,把name初始化为null,在把控制权返回给Chinese构造函数,接着执行这个构造函数,把age初始化为0,并且退出。此时,Chinese实例就已经成功了构造和初始化了。

我们注意构造函数的执行的顺序:最先调用的是总是基类的构造函数。先调用System.Object,在按照层次的结构由上到下进行。直到到达编译器要实例化的类为止。每个构造函数都初始化自己类中的字段。

六:在层次结构中添加带参数的构造函数:

首先是带一个参数的Person构造函数,它仅在提供其姓名时才实例化:

 

        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        protected Person(string name)
        {
            this.Name = name;
        }

 


在编译器试图为派生类创建默认的构造函数的时候,会产生一个编译错误,因为编译器为Chinese生成的默认构造函数视图调用一个无参数的Person构造函数。但是Chinese没有这样的构造函数。因此,需要为派生类提供一个构造函数,来避免这个错误(使用base调用父类的构造函数)。

    public class Chinese : Person
    {
        public Chinese(string name) : base(name)
        {
        }
    }

所以当我们new对象的时候会执行我们的构造函数,进行初始化赋值的操作。

 

            Person zsPerson = new Chinese("张三");

 


我们接下里做一个练习,首相我们声明一个Person,里面有我们的数据成员,包括名字、年龄、性别。我们自己声明的构造函数进行对字段的初始化赋值。

    public class Person
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        private int _age;
        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
        private char _gender;
        public char Gender
        {
            get { return _gender; }
            set { _gender = value; }
        }


        public Person(string name, int age, char gender)
        {
            this.Name = name;
            this.Age = age;
            this.Gender = gender;
        }
    }

子类的声明,继承父类的构造函数(因为编辑器默认调用时父类的无参数的构造函数,但是它没有了),所以需要显示的调用父类有参数的构造函数。子类也可以有自己的数据成员,接下来我们声明一个程序员类继承父类Person:

    //程序员类
    public class Programmer : Person
    {
        //工作时间
        private int _workYear;

        public int WorkYear
        {
            get { return _workYear; }
            set { _workYear = value; }
        }

        public void ProgrammerSayHello()
        {
            Console.WriteLine("我叫{0},我是一名程序猿,我是{1}生,我今年{2}岁了,我的工作年限是{3}年",this.Name,this.Gender,this.Age,this.WorkYear);
        }


        public Programmer(string name, int age, char gender, int workYear)
            : base(name, age, gender)
        {
            this.WorkYear = workYear;
        }
    }

客户段程序的调用:

            Programmer pro = new Programmer("程序猿", 23, '', 3);
            pro.ProgrammerSayHello();

            Console.ReadKey();

 

posted @ 2017-11-09 11:20  丢了蜡笔小新会哭〆  阅读(206)  评论(0编辑  收藏  举报