继承
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();