c#中虚函数的实际用途是什么?
10 回答
所以基本上如果在你的祖先类中你想要一个方法的某种行为。如果您的后代使用相同的方法但具有不同的实现,您可以覆盖它,如果它有一个虚拟关键字。
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',因为它未标记为“虚拟”、“抽象”或“覆盖”
如果您不使用覆盖,您将收到CS0108警告 'desc.Method()' 隐藏继承的成员 'base.Method()' 如果要隐藏,请使用新关键字。
要解决这个问题,请将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()
方法,因为飞行动物有一种特殊的进食方式。
所以这里想到的问题是:在我声明了a
type的变量Animal
并为其分配了一个新的 type 对象之后FlyingAnimal
,会a.eat()
做什么?这两种方法中的哪一种被调用?
这里的答案是:因为a
是 type Animal
,所以会调用Animal
's 方法。编译器很笨,不知道您要将另一个类的对象分配给a
变量。
这是virtual
关键字起作用的地方:如果您将方法声明为virtual void eat() {...}
,则基本上是在告诉编译器“请注意,我在这里做了一些您无法处理的聪明事情,因为您没有那么聪明”。因此编译器不会尝试将调用链接a.eat()
到这两个方法中的任何一个,而是告诉系统在运行时执行它!
因此,只有在代码执行时,系统才会查看a
' 的内容类型而不是其声明的类型并执行FlyingAnimal
' 方法。
你可能想知道:我到底为什么要这样做?为什么不一开始就说FlyingAnimal a = new FlyingAnimal()
呢?
这样做的原因是,例如,您可能有许多来自Animal
: FlyingAnimal
、SwimmingAnimal
、BigAnimal
等的派生类WhiteDog
。并且在某一时刻您想要定义一个包含许多Animal
s 的世界,所以您说:
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();
}
如您所见,每种动物都有自己的进食方法。只有使用虚函数才能实现这个功能。否则,每个人都将被迫以完全相同的方式“吃”:如类中最通用的eat
函数所述Animal
。
编辑:这种行为实际上是Java 等常见高级语言的默认行为。
像任何其他语言一样......当你想要多态时。这有很多用途。例如,您想抽象从控制台、文件或其他设备读取输入的方式。您可以拥有一个通用的阅读器接口,然后是多个使用虚拟函数的具体实现。
这允许实现后期绑定,这意味着在运行时而不是在编译时确定将调用哪个对象的成员。参见维基百科。
例如代理方法。即在运行时覆盖方法。例如,NHibernate 使用它来支持延迟加载。
基本上,虚拟成员允许您表达多态性,派生类可以具有与其基类中的方法具有相同签名的方法,并且基类将调用派生类的方法。
一个基本的例子:
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();
}
}
从这里:
在面向对象的编程中,虚函数或虚方法是一种函数或方法,其行为可以在继承类中被具有相同签名的函数覆盖。
例如,您有一个基类 Params 和一组派生类。您希望能够对存储从 params 派生的所有可能类的数组执行相同的操作。
没问题 - 声明虚拟方法,向 Params 类添加一些基本实现并在派生类中覆盖它。现在您可以遍历数组并通过引用调用方法 - 将调用正确的方法。
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
,则解析方法early
并根据指针或引用的声明类型选择调用的函数。
例子
让我们考虑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
事实上,如果您检查 System.Object 类,您会发现该方法被标记为虚拟。
namespace System
{
[NullableContextAttribute(2)]
public class Object
{
....
public virtual string? ToString();
....
}
}