1.1 简介
上一章中,我们讨论了C#实现基本OOP的概念。本章,我们将继续深入面向对象编程的概念,如多态性和虚函数、抽象基类、接口等。
1.2 C#中的多态性
C#多态性是指同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。有了多态性,在运行时就能方便实现派生类的方法。虚函数和多态性的关系很密切,虚函数允许派生类完全或部份重写基类函数的功能。下面我们来研究一段代码。
代码段1:
public class ShapeObj
{
public virtual void area()
{
System.Console.WriteLine(“这是一个虚area方法”);
}
}
在代码段1中,我们创建了一个类ShapeObj,并定义了一个称为area()的虚方法。area()方法在屏幕上输出一条消息。area()方法是一个虚方法,语法相当类似于通常的方法,但是必须指定virtual关键字。当使用此关建字后,不允许使用static、abstract、或override修饰符。从基类的对象调用派生类的方法时,需要在派生类中对基类的虚函数进行重载。在派生类中重新定义此虚函数时,方法名、返回值类型、参数个数、类型、顺序都必须与基类中的虚函数完全一致。派生类中声明加上override关建字,不允许有new,static或virtual修饰符。
图示:
public class Base //基类
{
public virtual void Func() //虚函数
{
Console.WriteLine(“Func of Base”);
}
}
public class Derived : Base //派生类
{
public override void Func()
{
Console.WriteLine(“Func of Derived”);
}
}
public static void Main()
{
Base B= new Derived();
B.Func();
}
Base类对象调用Func()也将调用Derived类的Func(),因为虚方法已被重写。
代码段2:
puhlic class Circle:ShapeObj
{
public override void area()
{
System.Console.WriteLine(“这是Circle的area()方法”);
}
}
public class Rectangle:ShapeObj
{
public override void area()
{
System.Console.WriteLine(“这是Rectangle的Area()方法”);
}
}
public class Square:ShapeObj
{
public override void area()
{
System.Console.WriteLine(“这是Square的area()方法”);
}
}
上面的代码段2中,定义了三个类(Rectangle、Circle和Square)。这些类是从ShapeObj类派生的,并且重写了ShapeObj类的area()方法。override关建字用于重写基类函数area()。下面我们将编写程序的Main()函数。这个函数将把代码的所有这些类组合到一起。
代码段3:
public class PolymorphismExp
{
public static void Main()
{
ShapeObj[] objArray = new ShapeObj[4];
objArray[0]=new ShapeObj();
objArray[1]=new Rectangle();
objArray[2]=new Circle();
objArray[3]=new Square();
foreach(ShapeObj iterateArray in objArray)
{
iterateArry.area();
}
}
}
以上示例输出结果为:
这是一个虚area方法
这是Rectangle的area方法
这是Circle的area()方法
这是square的area()方法
可以看到,因为它们之间存在继承关系,所以可以把它们作为继承类型添加到数组中。这样他们都拥有相同的area方法。就可以调用每个对象的该方法。如果不这样,我们需要为每个对象创建不同的数组。大量增大了编程工作。
示例2:
class SecondExp
{
public int firstMethod()
{
return(secondMethod() * thirdMethod());
}
public virtual int secondMethod()
{
return(10);
}
public int thirdMethod()
{
return(20);
}
}
class DerivedClass : SecondExp
{
public override int secondMethod()
{
return(30);
}
}
class Test
{
public static void Main()
{
DerivedClass objDerived =new DerivedClass();
System.Console.WriteLine(objDerived.firstMethod());
}
}
最后输出结果:600
请注意,在DerivedClass类中,没有重写firsMethod()方法时,从DerivedClass类的对象调用firstMethod()时,将调用SecondExp类的firstMethod(),这是因为DerivedClass类继承了SecondExp类。
现在,SecondExp类的firstMethod() 需要调用secondMethod()。在SecondExp类和DerivedClass类中都有secondMethod()。
要是采用通常的方法重写,并且不将有关的重写方法定义为虚方法,那么被调用的将会是SecondExp类的secondMethod(),并且输出将为200。
将SecondExp类的secondMethod()指定为虚函数,并在DerivedClass类中重写SecondExp类的secondMethod()方法。
由于对firstMethod()调用来自DerivedClass,因此将调用DerivedClass类中的重写方法。
多态性不仅仅是重写,而且是智能化重写。
重写和多态性之间的区别在于,调用哪种方法的决定是在运行时作出的。(基类的方法,还是实例化对象的类的方法)
多态性需要虚函数,而虚函数则需要方法重写,这就是多态性与重写之间的联系。
在实现多态的情型下,派生类的可访问性必须低于或等于基类的可访问性。
1.3 抽像基类
在某些时候,我们只需要对继承某个特定类,但不需要实例化该类的对象。这样的类称为抽像类。C#的抽像类以abstract修饰来标示。抽象基类不能实例化。在抽象基类中可以指定一个方法而不实现其代码主体。这意味着抽象基类保存着方法定义,而方法的实现则写在派生类中。这种没有实现的方法称为操作。我们来研究下示例。
示例3:
using System;
abstract class BaseClass
{
public abstract void abstractFunc();
public void nonAbstractFunc()
{
Console.WriteLine(“这是nonAbractFunc()方法!”);
}
}
class DerivedClass:BassClass
{
public override void abstractFunc()
{
Console.WriteLine(“这是abstractFunc()方法”);
}
}
class Test
{
static void Main()
{
DerivedClass objDerived=new DerivedClass();
BassClass objBase=objDerived;
objDerived.abstractFunc();
objDerived.abstractFunc();
}
}
显示结果为:这是abstractFunc()方法, abstractFunc()方法
上面的示例3中,声明了一个名为BaseClass的抽象基类。
抽象基类除了包含抽象方法(操作)外,还可以包含已实现的方法。
操作需要用abstract关键字来标记。
操作的定义始终以分号结束。
1.4 接口
上一节中我们说过了抽象类可以同时具有抽象方法和非抽象方法。如果需要定义只包含抽象方法的类,也就是纯抽象基类,我们就可以创建接口。
即,接口与纯抽象基类相似。它只能包含抽象方法,而不能包含方法的实现。记住,接口不能创建实例。接口拿来干嘛用呢?接口仅用于指示实现特定接口的类必须实现该接口所列出的成员。也就是接口是对类中方法、属性、事件或索引器的约束条件。
下面我们来研究下代码段。
代码段4;
public interface IFile
{
int delFile();
void disFile();
}
接口我们使用interface关键字来定义。接口名通常以大写字母I开头,以表示它是一个接口。
接口的成呗没有访问修饰符;这些修饰由类继承,以设置其可见性。接口的成员必须是方法、属性、事件或索引器,接口不能包含常数、字段、运算符、实例构告函数或者类型、也不能包含静态成员。
当一个类使用一个接口时,我们通常称之为这个类“实现”了该接口。
代码段5:
public class MyFile:IFile
{
public int defile()
{
System.Console.WriteLine(“删除文件实现!”);
return(0)
}
public void disFile()
{
System.Console.WriteLine(“自动获取文件成功!”);
}
}
Class Test
{
static void Main()
{
MyFile objMyFile = new MyFile();
objMyFile.disFile();
int retValue = objMyFile.delFile();
}
}
输出结果为:删除文件成功! 自动获取文件成功!
注意:类MyFile实现了接口IFile,与继承一样,这里使用的是“:”操作符。
在大括号中,定义了接口中方法的实现。这里有一件很有意思的事件:与抽象基类不同我们没有重写方法,而直接实现了它们,因此根本不需要指定关键字override。
Main()方法中实例化类的方式以及调用这些方法的方式没有改变。
类可以实现接口,也可以继承其他类。
下面我们来看个例子。
代码段 6
using System;
public class BaseforInterface
{
public void open()
{
Console.WriteLine(“这是BaseforInterface的open方法”);
}
}
现在,我们让MyFile类来继承这个类,同时实现IFile接口
代码段7
using System
public class MyFile:BaseforInterface , IFile
{
public int delFile()
{
Console.WriteLine(“DelFile实现!”);
return(0);
}
public void disFile()
{
Console.WriteLine(“DisFile实现!”);
}
}
class Test
{
static void Main()
{
MyFile objMyFile = new MyFile();
ojMyFile.disFile();
int retValue = objMyFile.defile();
objMyFile.open();
}
}
输出结果为:DsFile实现! DelFile实现! 这是BaaseforInterface的open方法
注意:如果需要继承类并同时实现接口,我们以逗号区分。
1.4.1 多接口实现
C#中不允许有多重继承的。但是它允许多接口实现。也就是一个类可以实现多个接口。下面我们来研究下代码。
代码段8:
public interface IFileTwo
{
void applySecondInterface();
}
下面我们用MyFile类实现这个接口。
代码段9:
using System;
public class MyFile:BaseforInterface,IFile,IFileTwo
{
public int delFile()
{
Console.WriteLine(“DelFile实现!”);
return (0)
}
public void disFile()
{
Console.WriteLine(“DisFile实现!”);
}
public void applySecondInterface()
{
Console.WriteLine(“ApplySecondInterface实现!”);
}
}
class Test
{
static void Main()
{
MyFile objMyFile = new MyFile();
objMyFile.disFile();
int retValue=objMyFile.delFile();
objMyFile.open();
objMyFile.applySecondInterface();
}
}
输出结果为:
DisFile实现!
DelFile实现!
这是BaseforInterface的open方法
ApplySecondInterface实现!
只要不存在命名冲突,C#中完全接受多接口实现。如果出现相同命名包括类型和参数,则会产生问题,这样将无法指定要实现哪个接口。
但,我们可以通过显式接口实现来解决这个问题。
1.4.2 显式接口实现
显式接口实现非常简单。我们来看一个例子。
代码段10:
public interface IFile
{
int delFile();
void disFile();
}
public interface IFileTwo
{
void applySecondInteface();
void disFile();
}
public class MyFile: BaseforIntface,IFile,IFileTow
{
….
void IFile.disFile()
{
System.Console.WriteLine(“DisFile的IFile实现”);
}
void IFileTwo.disFile()
{
System.Console.WriteLine(“DisFile的IFileTwo实现”);
}
…
}
1.4.3 接口继承
接口具有不变性,但这并不意味着接口不再发展。和类相似接口也可以继承和发展。接口继承和类的继承不同,首先,类继承不仅是说明继承,而且也是实现继承;而接口继承只是说明继承。也就是说,派生类可以继承基类的方法实现,而派生的接口只继承了父接口的成员方法说明。其次,C#中类继承只允许单继承,但是,接口继承允许多继承,一个子接口可以有多个父接口。接口可从零或多个接口中继承。从多个接口中继承时,用“:”后跟被继承的接口的名字,多个接口的名字间用逗号分割。被继承的接口应该是可以访问得到的,比如从private 类或internal类型的接口中继承就是不允许的。接口不允许直接或间接的从自身继承。可以将多个接口组合到一起来创建新的接口。其语法非常类似于继承的语法,不同之处在于可以合并多个接口来形成单个接口。
假定需要将两个接口IFile和IFileTwo合并为一个接口IallFile,需要编写以下代码。
代码段11:
interface IAllFile:IFile,IFileTwo
{
//如果需要,除了IFile和IFileTwo操作之外,还可以添加更多操作。
}
1.5 小结
多态性和虚函数关系非常密切
需要从基类的对象调用派生类方法时,可以使用虚函数。
多态性不只是重载或重写,而是智能重写。
重写和多态性之间的区别在于,在多态性中,要调用哪个方法的决定是在运行时做出的。
多态性需要虚函数,而虚函数则需要方法重写。
抽象基类是至少包含一个抽象成员,不包括实现方法的类。
不能创建抽象基类的新实例。
没有实现的方法称为操作。
接口是纯抽象基类。它只能包含抽象方法,而不能包含任何方法的实现。
一类可以实现多个接口:事实上,类能够从另一个类继承,也能够实现接口。
1.6 练习
1. 请观察下面的代码并找出其中的错误,选择一个列出了代码中全部错误的正确答案。
Class SecondExp
{
public int firstMethod()
{
return (100);
}
}
class DerivedClass:SecondExp
{
public override int secondMethod()
{
return(399);
}
}
class Test
{
public static void Main()
{
DerivedClass objDerived = new DerivedClass();
System.Console.WriteLine(objDerived.firstMethod());
}
}
a. 类SeconExp中没有将任何方法标记为进行重写。
b. 没有找到合适的方法进行重写
c. A和B都是
2. 抽象基类不能包含方法实现
a. 对 b.错
3. 抽象基类________________实例化。
a.可以 b.不可以
4. _______________可以看作类的模具。
a.抽象类 b.接口 c.虚方法
5. 在C#中,不允许多接口实现。
a.对 b.错
3.7 作业
1.创建一个称为Animals的抽象类。它应当包含一个称为saySomething()的方法,该方法既不返回任何类型,也不带任何参数。接下来,从Animals派生一个称为Cats的类。在派生的类中实现saySomething()方法。SaySomeghing()必须在用户的控制台上打印以下信息:
猫喵喵喵叫!
从Animals抽象基类派生另一个称为Dogs类。这一次saySomething()方法需要在用户的控制台上打印以下消息:
狗汪汪叫!
2.重复上面的练习,但是这次不要创建抽象基类,而是通过使用接口达到相同的效果。
3. 创建一个称为Imammals的接口,该接口继承上一个练中创建的接口。这个接口应当具有一个附加操作,下表对这个操作进行了说明:
标识符 返回类型 参数
getBodyTemp Int() String
标识符 |
返回类型 |
参数 |
getBodyTemp |
Int() |
String |