在C#中实用虚函数的用途
理解虚函数的实际用途的关键是记住:某个类的对象可以被赋给该对象派生类的对象。例如:
class Animal { public void eat() {...} } class FlyingAnimal : Animal { public void eat() {...} } Animal a = new FlyingAnimal();
。Animal类有一个函数eat(),通常描述了动物如何进食(例如把物体放到嘴里然后吞下去)。然而,FlyingAnimal类应该定义一种新的eat()方法,因为飞行动物有一种特殊的进食方式。因此,在这里提出的问题是,在我声明了类型为Animal的变量a并将其分配给类型为FlyingAnimal的新对象之后,a.eat()会做什么?哪种方法被调用了?答案是:由于a是类型为Animal的,它将调用Animal的方法。编译器很蠢,不知道你将一个不同类的对象分配给a变量。这就是虚拟关键字发挥作用的地方:如果将方法声明为virtual void eat() {...},你基本上是告诉编译器“小心,我正在做一些聪明的事情,你无法处理,因为你不如我聪明”。因此,编译器不会尝试将调用a.eat()链接到这两个方法中的任何一个,而是告诉系统在运行时执行!因此,只有当代码执行时,系统才会查看a的内容类型而不是它声明的类型,并执行FlyingAnimal的方法。你可能会想,为什么我要这样做?为什么不从一开始就说 FlyingAnimal a = new FlyingAnimal()
?原因是,例如,你可能有很多从Animal
派生出来的类:FlyingAnimal
,SwimmingAnimal
,BigAnimal
,WhiteDog
等等。在某一时刻,你想定义一个包含许多Animal
的世界,所以你会说:
Animal[] happy_friends = new Animal[100];
我们有一个有100只快乐的动物的世界。你在某个时候初始化它们:
... happy_friends[2] = new AngryFish(); ... happy_friends[10] = new LoudSnake(); ...
在一天结束前,你希望每个人都能吃饱再睡觉。所以你想说:
for (int i=0; i<100; i++) { happy_friends[i].eat(); }
因此,你可以看到,每种动物都有自己的进食方式。只有使用虚函数才能实现这种功能。否则,每个人都会被迫以与Animal
类内最一般的eat
函数描述的方式相同地“进食”。
编辑:
事实上,这种行为在像Java这样的常见高级语言中是默认的。
基本上,如果您的祖先类中某个方法需要特定的行为。如果您的后代使用相同的方法但具有不同的实现,那么如果它具有“虚拟”关键字,您可以进行“覆盖”。
using System; class TestClass { public class Dimensions { public const double pi = Math.PI; protected double x, y; public Dimensions() { } public Dimensions (double x, double y) { this.x = x; this.y = y; } public virtual double Area() { return x*y; } } public class Circle: Dimensions { public Circle(double r): base(r, 0) { } public override double Area() { return pi * x * x; } } class Sphere: Dimensions { public Sphere(double r): base(r, 0) { } public override double Area() { return 4 * pi * x * x; } } class Cylinder: Dimensions { public Cylinder(double r, double h): base(r, h) { } public override double Area() { return 2*pi*x*x + 2*pi*x*y; } } public static void Main() { double r = 3.0, h = 5.0; Dimensions c = new Circle(r); Dimensions s = new Sphere(r); Dimensions l = new Cylinder(r, h); // Display results: Console.WriteLine("Area of Circle = {0:F2}", c.Area()); Console.WriteLine("Area of Sphere = {0:F2}", s.Area()); Console.WriteLine("Area of Cylinder = {0:F2}", l.Area()); } }
编辑:评论中的问题
如果我在基类中不使用虚拟关键字,是否有效?
如果您在后代类中使用“override”关键字,则不起作用。您将生成编译器错误“函数1”:无法覆盖继承的成员“函数2”,因为它未标记为“virtual”、“abstract”或“override”CS0506
如果您不使用覆盖,则会收到警告CS0108“desc.Method()”隐藏了继承的成员“base.Method()”,如果有意隐藏,请使用new关键字。
要解决此问题,请在您要隐藏的方法前面放置“new”关键字。
例如:
new public double Area() { return 2*pi*x*x + 2*pi*x*y; }
是否必须在派生类中覆盖虚拟方法?
不需要,如果您不覆盖该方法,则后代类将使用它继承的方法。