59

c#中虚函数的实际用途是什么?

4

10 回答 10

89

所以基本上如果在你的祖先类中你想要一个方法的某种行为。如果您的后代使用相同的方法但具有不同的实现,您可以覆盖它,如果它有一个虚拟关键字。

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; 
  }

..并且必须在派生类中覆盖虚拟方法吗?
不,如果您不覆盖该方法,则后代类将使用它继承自的方法。

于 2009-06-30T07:00:30.323 回答
76

理解虚函数的实际用法的关键是要记住,某个类的对象可以分配给从第一个对象的类派生的类的另一个对象。

例如:

class Animal {
   public void eat() {...}
}

class FlyingAnimal : Animal {
   public void eat() {...}
}

Animal a = new FlyingAnimal();

该类Animal有一个函数eat(),通常描述动物应该如何进食(例如,将物体放入嘴中并吞咽)。

但是,FlyingAnimal该类应该定义一个新eat()方法,因为飞行动物有一种特殊的进食方式。

所以这里想到的问题是:在我声明了atype的变量Animal并为其分配了一个新的 type 对象之后FlyingAnimal,会a.eat()做什么?这两种方法中的哪一种被调用?

这里的答案是:因为a是 type Animal,所以会调用Animal's 方法。编译器很笨,不知道您要将另一个类的对象分配给a变量。

这是virtual关键字起作用的地方:如果您将方法声明为virtual void eat() {...},则基本上是在告诉编译器“请注意,我在这里做了一些您无法处理的聪明事情,因为您没有那么聪明”。因此编译器不会尝试将调用链接a.eat()到这两个方法中的任何一个,而是告诉系统在运行时执行它!

因此,只有在代码执行时,系统才会查看a' 的内容类型而不是其声明的类型并执行FlyingAnimal' 方法。

你可能想知道:我到底为什么要这样做?为什么不一开始就说FlyingAnimal a = new FlyingAnimal()呢?

这样做的原因是,例如,您可能有许多来自Animal: FlyingAnimalSwimmingAnimalBigAnimal等的派生类WhiteDog。并且在某一时刻您想要定义一个包含许多Animals 的世界,所以您说:

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 等常见高级语言的默认行为。

于 2013-08-29T13:17:59.663 回答
7

像任何其他语言一样......当你想要多态时。这有很多用途。例如,您想抽象从控制台、文件或其他设备读取输入的方式。您可以拥有一个通用的阅读器接口,然后是多个使用虚拟函数的具体实现。

于 2009-06-30T06:58:11.107 回答
4

这允许实现后期绑定,这意味着在运行时而不是在编译时确定将调用哪个对象的成员。参见维基百科

于 2009-06-30T09:17:34.647 回答
4

例如代理方法。即在运行时覆盖方法。例如,NHibernate 使用它来支持延迟加载。

于 2009-06-30T06:58:23.023 回答
3

基本上,虚拟成员允许您表达多态性,派生类可以具有与其基类中的方法具有相同签名的方法,并且基类将调用派生类的方法。

一个基本的例子:

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();
    }
}
于 2009-06-30T07:00:14.690 回答
1

这里

在面向对象的编程中,虚函数或虚方法是一种函数或方法,其行为可以在继承类中被具有相同签名的函数覆盖。

于 2009-06-30T06:57:57.627 回答
1

例如,您有一个基类 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();
    }
 }
于 2009-06-30T06:58:37.610 回答
1

c#中虚函数的使用

虚函数多用于覆盖具有相同签名的派生类中的基类方法。

当派生类继承基类时,派生类的对象是对派生类或基类的引用。

虚函数由编译器延迟解析(即运行时绑定)

virtual在基类中,根据所引用对象的实际类型调用最派生类的函数实现,而不管指针或引用的声明类型如何。如果不是virtual,则解析方法early并根据指针或引用的声明类型选择调用的函数。

于 2013-10-07T18:13:21.977 回答
1

例子

让我们考虑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();
        ....
    }
}
于 2020-03-05T10:45:13.543 回答