用《叩响C#之门》复习C#基础知识 第九章 面向对象编程:继承
1、解决派生类中不能使用基类的私有成员 的三种方法
1)将基类的私有成员变为公有,这种方法会使成员丧失了封装性,不可取。
2)可以将成员改为protected成员(受保护成员),不能被外界使用,但可以被派生类使用。
3)为私有成员变量设计一个公有的属性(受保护的属性应该也可以)
2、虚函数和重写
在基类中用关键字virtual声明虚函数,virtual放于访问修饰符后面。
在派生类中用关键字override重写基类的虚函数,override也放于访问修饰符后面,重写时,除方法实现部分可以更改以外,其他如方法的访问修饰符、返回类型、参数列表都必须与基类中的虚函数完全一致。
属性也可以重写,属性其实就是函数,所以也可以写成虚属性。静态函数不可重写!
注意:virtual修饰符不能与static、abstract、override修饰符一起使用
虚拟成员和抽象成员不能为私有,即修饰符不能是private,在派生类中重写时,也不能定义成private。
基类中的虚函数也可以被基类实例化后的对象所使用。
重载和重写区别
重载函数发生在同一个类中的同名函数之间,重写函数发生在基类和派生类之间。重载是指同一个类中有若干个名称相同但参数不同的函数,调用函数时,系统会根据实参情况,调用参数完全匹配的那个函数。重写是指在继承关系中,在派生类中重写由基类继承来的函数,这时基类和派生类中就有两个同名的函数,系统根据对象的实际类型调用相应版本的函数,当对象为基类时,系统调用基类中的函数,当对象为派生类时,系统调用派生类中被重写的函数。
虚属性使用举例:
using System; using System.Collections.Generic; namespace Test { public class Mammal { protected string name; protected virtual string Name { get { return this.name; } set { this.name = value; } } } public class Cat:Mammal { protected override string Name { get { return "The cat's name is "+this.name; } set { this.name ="cat--" + value; } } public Cat(string nameVar) { this.Name = nameVar; } public void DisplayName() { Console.WriteLine(this.Name); } } public class Program { static void Main() { Cat[] cat=new Cat[7]; for(int i=0;i<7;i++) { cat[i]=new Cat("Cat"+i); } foreach(Cat catVar in cat) { catVar.DisplayName(); } } } }
3、普通函数的隐藏
只能重写基类中的虚函数,普通函数不能重写,要在派生类中修改基类的普通函数,需要用到new关键字隐藏基类中的函数。new关键字可以省略,但最好不要这样做。new放在访问修饰符前后都可以,一般习惯性都是访问修饰符在最前面,关键字在后面。这种隐藏,派生类普通函数与基类中的普通函数也必须有相同的函数名、参数列表,但访问修饰符、返回类型可以不同。
4、base关键字(原书中表述有点欠妥)
每个对象都有一个指向自身的引用符即this,同样,可以通过base关键字来访问基类成员(书中表述base是指向基类的引用符,这种表述有点不妥。)
base常用于1)在派生类对象初始化时和基类进行通信(即调用派生类构造函数前调用基类构造函数);2)访问基类的公有成员和受保护成员,私有成员不可访问。
自己分析的(不知道合不合适):base应该被看做是派生类对象去访问基类成员(包括被覆写或被隐藏的成员),应该是反映一个机制的关键字而已,不能当做引用符(因为不能直接拿base当做引用符去赋值给其他引用符),如果硬要将其理解为引用符的话,就必须将访问的基类成员分为字段和方法两方面来阐述。
1)针对基类的字段,通过base去访问托管堆中的GC堆的基类字段。
2)针对基类的方法,通过base去访问托管堆中的Loader Heap堆中的基类方法表。
该知识点还应再深入一些。
注意:this和base关键字都不能用于静态方法中(因为都是放在对象的方法中使用的),也就是说在Main()方法中也不可以使用。
5、抽象类和抽象函数
抽象类用关键字abstract声明,抽象类不能实例化为对象,其存在的目的就是派生其他类,只能作为其他类的基类而存在。
不仅能声明抽象类,还可以声明抽象函数(abstract function)
抽象函数是一种特殊的虚函数,只能定义在抽象类中,抽象函数没有任何执行代码,需要在派生类中用重写的方式具体实现,除了抽象函数外,还可在抽象类中定义抽象属性,抽象属性也没有具体实现代码,必须在派生类中重写。
public abstract int Age
{
get;
set;
}
虽然不能实例化对象,但用抽象类声明的引用符还是可以用的,用法同普通的基类一样。
6、密封类和密封函数
密封类(sealed class)是一种不能被继承的类,用sealed关键字声明。
sealed class A
{…}
同样,如果希望一个函数不能被派生类重写,可以用sealed关键字把它声明为密封函数。
class B
{
public sealed override void F(){…}
}
当我们重写函数时,如果想保证这是最后一次重写,就可以使用sealed关键字。
7、派生类的构造函数
创建对象时,系统先调用基类的构造函数,初始化基类的变量,然后调用派生类的构造函数,初始化派生类的变量是一个由基类向派生类逐步构建的过程。删除对象时,先调用派生类的析构函数,销毁派生类的变量,然后调用基类的析构函数,销毁基类的变量,是一个由派生类向基类逐步销毁的过程。
1)带参数的构造函数
在派生类的构造函数中,可以显式地初始化从基类继承来的public和protected成员,也可以通过基类public属性初始化基类的私有成员。
2)显式调用基类的构造函数
因为派生类不能使用基类的私有变量,所以不能通过派生类的构造函数初始化基类的私有变量。但我们可以通过显式调用基类的构造函数实现,当然也可以通过显式调用基类的构造函数初始化基类的公有变量和受保护变量。
显式调用基类构造函数的语法:
访问修饰符 派生类名(派生类构造函数的参数列表):base(基类构造函数的参数列表)
在派生类的构造函数中就不再需要对基类构造函数初始化的成员变量进行初始化了。
练习思考:用了四个类,分别继承,来测试base和this的用法
using System; using System.Collections.Generic; namespace Test { public abstract class Animal { protected string name; public abstract string Name { get; set; } public abstract void DisplayName(); } public class Vertebrata:Animal { public override string Name { get { return "The vertebrata"+this.name; } set { this.name = "vertebrata--"+value; } } public override void DisplayName() { Console.WriteLine("Vertebrata类的方法" + this.Name); } public Vertebrata() { name = "Vertebrata------"; } } public class Mammal:Vertebrata { public void Test() { Console.WriteLine("Test!"); } public Mammal() { name = "Mammal------"; } //public string name; public override string Name { get { return "The mammal's name is " + this.name; } set { this.name = "mammal--" + value; } } protected string ppp(int var1) { return("12"); } public override void DisplayName() { Console.WriteLine("hahaha"+this.Name); } //public void ReturnBase() //{ // //Mammal mal1=this; // //return mal1; // base.DisplayName(); // //Console.WriteLine(base.Name); //} } public class Cat:Mammal { public Cat() { name = "Cat------"; } public new int ppp(int var1) { return var1; } public override string Name { get { return "The cat's name is " + this.name; } set { this.name = "cat--" + value; } } public Cat(string nameVar) { this.Name = nameVar; } public void ReturnBase() { base.DisplayName(); } public new void DisplayName() { Console.WriteLine("pppppp"+this.Name); } } public class Program { static void Main() { Animal ani1 = new Cat(); ani1.DisplayName(); Animal ani2 = new Mammal(); ani2.DisplayName(); Cat cat1 = new Cat(); cat1.DisplayName(); cat1.ReturnBase(); } } }
输出: