在 C# 中,虚函数的实际用途是什么?
基本上,如果在您的祖先类中,您希望某个方法具有某种行为。如果您的后代使用相同的方法但具有不同的实现,则可以通过加上virtual关键字来进行覆盖。
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());
}
}
编辑: 问题在评论中提出
如果我在基类中不使用virtual关键字,会起作用吗?
如果您在派生类中使用override
关键字,则不会起作用。您将生成编译器错误CS0506'function1':无法覆盖继承的成员'function2',因为它未标记为“virtual”、“abstract”或“override”。
如果您没有使用override,则会收到警告CS0108 'desc.Method()'隐藏了继承的成员'base.Method()'。如果打算进行隐藏,请使用new关键字。
要解决此问题,请在您正在隐藏的方法前面放置new
关键字。
例如:
new public double Area()
{
return 2*pi*x*x + 2*pi*x*y;
}
..是否强制在派生类中重写虚函数?
不是的,如果你不重写该方法,则派生类将使用继承自基类的方法。
理解虚函数的实际用法的关键在于记住,一个特定类的对象可以被分配给另一个从第一个对象的类派生的类的对象。
例如:
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
关键字发挥作用的地方:如果你将方法声明为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
类中描述的方式进行“进食”。
编辑: 在像Java这样的通用高级语言中,这个行为实际上是默认的。
就像其他编程语言一样,当你需要使用多态时,它可以有很多用途。例如,你想要抽象化从控制台、文件或其他设备读取输入的方式,你可以使用一个通用的读取器接口,然后使用虚函数来实现多个具体的读取器。
虚拟成员基本上允许您表达多态性,派生类可以具有与其基类中的方法相同签名的方法,基类将调用派生类的方法。
一个基本示例:
public class Shape
{
// A few example members
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }
// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}
class Circle : Shape
{
public override void Draw()
{
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
}
}
class Rectangle : Shape
{
public override void Draw()
{
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
}
}
class Triangle : Shape
{
public override void Draw()
{
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
}
}
class Params {
public:
virtual void Manipulate() { //basic impl here }
}
class DerivedParams1 : public Params {
public:
override void Manipulate() {
base.Manipulate();
// other statements here
}
};
// more derived classes can do the same
void ManipulateAll( Params[] params )
{
for( int i = 0; i < params.Length; i++ ) {
params[i].Manipulate();
}
}
C#中虚函数的使用
虚函数主要用于在派生类中以相同的签名重写基类方法。
当派生类继承基类时,派生类对象是对派生类或基类的引用。
虚函数由编译器进行迟绑定(即运行时绑定)。
在基类中使用 virtual
关键字后,根据所引用对象的实际类型而非指针或引用的声明类型,调用最终派生类的函数实现。如果没有使用 virtual
,则方法会通过早期绑定解析,并根据指针或引用的声明类型选择要调用的函数。
例子
让我们考虑 System.Object 中的 ToString() 方法。由于这个方法是 System.Object 的成员,它会被所有类继承,并为它们提供 ToString() 方法。
namespace VirtualMembersArticle
{
public class Company
{
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
Company company = new Company() { Name = "Microsoft" };
Console.WriteLine($"{company.ToString()}");
Console.ReadLine();
}
}
}
VirtualMembersArticle.Company
假设我们想要改变Company类中从System.Object继承的ToString()方法的标准行为。为了达到这个目标,只需要使用override关键字声明该方法的另一个实现即可。
public class Company
{
...
public override string ToString()
{
return $"Name: {this.Name}";
}
}
Name: Microsoft
namespace System
{
[NullableContextAttribute(2)]
public class Object
{
....
public virtual string? ToString();
....
}
}