继承:
1、在一个类被认为是一个类的特例时,继承用来从已存在的类中派生出新类型。
2、派生类自动继承基类的数据结构和行为。
3、C#中表示一个类从另一个派生的语法是,在类的声明后面放一个冒号和基类名称。
public class Person
{
string name;
public string Name
{
get{return name;}
set{name=value;}
}
}
//从Person派生出Student类.
public class Student:Person
{
//即便在类的内部不定义任何特征,该类仍拥有一个字段(name)和一个属性(Name),它们是从Person类继承的(实际上还有其他Person类从Object类继承的特征)
}
重用基类行为:baas关键字
如果派生类中提供了基类的一个方法签名相同的方法(相同的方法名和参数列表),则我们就覆载了该基类方法。什么时候想要/需要这么做?在派生类需要对一个消息做出与基类稍有不同的特别反馈动作时,可以这样做。
public class Person
{
private string name;
private string ssn;
public string Name
{get;set;}
public string Ssn
{get;set;}
//Person对象描述自己
public virtual string GetDescription(){
return Name + "(" + Ssn+")"; //例如"John Doe (123-34-6789)"
}
}
public class Student :Person
{
private string major;
public string Major
{
get;set;
}
//Stundent对象返回一个与父类(Person)不同的描述。所以在9子类中覆载GetDescription 方法。
public override string GetDescription()
{
return Name +"(" + Ssn +") [" +Major+ "]"; //例如"John Doe (123-34-6789) [Math]"
}
}
派生类方法可以使用base关键字来调用基类的相同方法。这一特征让我们得以降低代码冗余,因为可以重用基类所做的工作,而只添加派生类方法所需的额外代码。
让我们修改Student类的GetDescription方法,让它先调用相同方法的Person类版本。
public class Student :Person
{
private string major;
public string Major
{
get;set;
}
//和Person中定义的方法名相同——所以该方法覆载了被继承的版本
public override string GetDescription()
{
//注意,现在调用父类的方法,重用里面的代码。
return base.GetDescription + "[" +Major+ "]"; //例如"John Doe (123-34-6789) [Math]"
}
}
跟this关键字通常指向本方法所属对象的情形类似,base也在类的方法中指向父类的特征。
继承和构造器
构造器的继承,与方法的继承不同。我们将通过下面的例子来演示几个复杂之处。先为Person类声明一个有两个参数的构造器;
public class Person
{
private string name;
private string ssn;
public string Name {get;set;}
public string Ssn {get;set;}
//只声明一个构造器
public Person(string n ,string s)
{
Name = n;
Ssn = s;
}
}
Person类现在只能识别一个构造器签名——有两个参数的构造器——因为默认的无参数的构造器已被忽略。
现在,比如说,我们从Person类派生出Student类之后,还想为Student类定义两个构造器——有两个参数和三个参数的构造器。因为构造器不能被继承,我们不得不为Student类编写有两个参数的构造器。如下所示:
public class Student : Person
{
private string major;
public string Major { get;set; }
//有两个参数的构造器
public Student(string n ,string s)
{
//注意,这个构造构造器冗余了父类的构造器——一会儿来修正它。
Name =n; //冗余
Ssn = s; //冗余
Major = null;
}
//有三个参数的构造器
public Student(string n ,string s ,string m){
//更多冗余!
Name = n; //冗余
Ssn =s; //冗余
Major = m;
}
// 没有声明其他构造器。
}
幸运的是,我们可以重用父类中构造器的代码而无需在派生类中重复这些代码的逻辑。要做到这一点,就得利用前面讨论方法重用时提到过的base关键字。要明确地重用一个特定父类中的方法,可以用一下语法:base(可选参数列表),如下面改写过的Student类所示
public class Student : Person
{
private string major;
public string Major { get;set; }
//有两个参数的构造器
//调用有两个参数的Person类构造器,传入n和s两个参数。
public Student(string n ,string s):base(n,s)
{
//现在只需关心Student类需要专门做的事情
Major = null;
}
//有三个参数的构造器
//调用有两个参数的Person类构造器,传入n和s两个参数。
public Student(string n ,string s ,string m):base(n,s){
Major = m;
}
// 没有声明其他构造器。
}
同时使用this(....)语法重用本类的构造器类似,如果派生类的构造器中调用了基类的构造器,则基类构造器代码将先执行。
base()的隐式调用
不管我是否在派生类的构造函数中用base(...)构造调用基类的构造器,C#始终会试图执行所有祖先类的构造器。在开始执行给定类的构造器代码前,会按照类继承的层次,从一般到特殊(从上到下),逐层调用它们的构造器。
例如,当创建一个Student对象时,我们实际上创建了一个Object对象,一个Person对象和一个Student对象!所以,在调用Student类的构造器时,Object类的构造器将会先执行,接着是Person构造器,最后才是Student类的构造器。因此,如果不利用base(...)来编写Student的构造器的话,如下所示:
public class Student : Person
{
string major;
//有两个参数的构造器
//此处没有调用任何特定基类的构造器。
public Student(string n,string s)
{
//现在我们能专于Student特有的特性
major = null;
}
}
和下面的代码没有区别(注意粗体字部分):
public class Student:Person
{
//有两个参数的构造器
//此处没有调用任何特定基类的构造器。
public Student(string n,string s) : base()
{
//我们现在能专注于Student特有的特性
major= null;
}
}
这样就明确调用了Person类的无参数构造器。
这是第一反应,因为我们说过,继承表现为一个“is a”关系——Student也是Person,Person也是Object——因此,创建Object对象和创建Person对象时必要做的一切,也是创建Student对象所需要做的。问题是,如果我们定义了多个基类,哪一个将会被调用?
要回答这个问题,需要讨论集中不同的情况:
情况1:派生类没有声明自己的构造器
我们已经知道,如果派生一个类似Student的类,无需声明任何构造器,C#将会给派生类提供一个默认的无参数的构造器。当创建一个派生类的新对象时,所有祖先类的无参数的构造器会从上到下一次被调用,直到派生类自己的默认构造器被调用为止。这意味着,如果从类A派生出类B,并打算使用类B的默认无参数构造器,则基类A中明确定义的或默认的无参数构造器将先被调用。
下面的程序将无法被编译(我们将过会儿解释为什么会这样):
public class person
{
private string name;
//有一个参数的构造器——有了它,Person类的默认构造器就“没”了
public Person(string n){
name =n;
}
//无参数的构造器在本例中将不存在
}
public class Student : Person
{
string major;
//没有为Student指明构造器!所以,将使用默认的无参数构造器。
}
试图编译Student,将得到下面的错误信息:
error cs1501: No overload for method 'Person' takes '0‘ arguments
这是因为,C#编译器试图为Student类创建一个无参数的构造器,但要这样做,编译器得调用Person类的无参数构造器——但是Person中却没有这样的构造器!基本上,编译器会试图为Student类产生以下默认构造器:
public Student() :base(){
//初始化一个仅有“骨架”的student——细节从略
}
这个难题有以下两种解决方案:
1、为Person类明确编写一个无参数的构造函数,取代那个“丢失”的默认Person类构造器,当编译器创建默认的Student构造器时,可以利用到它,这是一般采用的手段;
2、或者,总是通过base关键字的使用,明确地在Student类的构造器中调用Person类的特定构造器。
后面这种解决方案将在下面介绍
情况2:派生类明确声明了一个或多个构造器
这可以分为两种情况:
子情况2A:派生类不明确调用基类的构造器
如果派生类不用base(....)构造明确地调用基类的构造器,基类的无参数构造器还是会被调用,如情况1所述。因此,下面的代码不能被编译:
public class Person
{
//细节从略
//有一个参数的构造器——有了它,Person类的默认构造器就“没”了
public Person(string n){
Name = n;
}
}
public class Student:Person{
//细节从略
//声明一个Student构造器,但不调用Person类的构造器。
public Student(string n,string m){
Name =n;
Major =m;
}
}
编译Student类,会得到编译错误信息:
No constructor matching Person() found in class person(实际上,会得到和上例一样的错误信息)
子情况2B:派生类明确调用了基类的构造器
在派生类中用base(...)构造器调用特定父类的构造器,从而解决子情况2A中的问题。
public class Person
{
string name;
//有一个参数的构造器——有了它,Person类的默认构造器就“没”了
public Person(string n){
Name = n;
}
//对person类不提供其他构造器
}
public class Student:Person
{
string major;
//构造器
//明确调用Person类的一个参数的构造器,传入n的值
public Student(string n, string m) : base(n){
major =m;
}
}
代码会被正常编译!