是直到运行时才创建的方法的虚拟方法

Are virtual methods for methods that are not created until runtime?

本文关键字:方法 虚拟 创建 运行时      更新时间:2023-10-16

我在理解c++中虚方法的目的时遇到了一点麻烦。如果一个方法的对象不是在编译时创建的,那么它必须是虚拟的吗?例如,如果您必须在运行期间挑选农场动物,那么该动物的所有方法都需要是虚拟的,因为在用户挑选一个动物之前,您不知道它是否会被创建。如果我说错了,请纠正我。

不,这是完全错误的。如果需要根据对象的类型选择方法,并且在编译时不知道类型,则方法必须是虚拟的。如果你的代码看起来像这样:

Animal *x;
if(y==2)
{
    x = new Animal();
    x->DoSomething();
}

编译器在运行时知道x的类型是'Animal'。所以它知道要调用哪个版本的DoSomething。但是看看这段代码:

Animal *x;
if(y==1) x=new Zebra();
else if (y==2) x=GetSomeAnimal();
else x=new Giraffe();
x->DoSomething();

这里,x的类型在编译时是未知的。它可以是斑马,可以是长颈鹿,也可以是GetSomeAnimal函数返回的任何类型的动物。没有办法知道DoSomething的调用是否应该调用Zebra::DoSomething, Giraffe::DoSomething或其他东西。所以Animal::DoSomething必须是虚拟的

为了显示它与将要创建的内容无关,考虑如下:

void MyFunction(Animal &x)
{
    x.DoSomething();
}
void MyOtherFunction(int x)
{
   Giraffe g;
   Zebra z;
   if(x==2) MyFunction(g);
      else MyFunction(f);
}

这里非常清楚,将创建一个Giraffe和一个Zebra。但是如果Animal::DoSomething不是虚拟的,MyFunction将两次调用Animal::DoSomething,而不是在长颈鹿上调用Giraffe::DoSomething,在斑马上调用Zebra::DoSomething。(当然,如果这是你想要的,不要使方法成为虚方法。)

在这里阅读关于虚方法的目的。我还建议你看一看"面向对象编程"这本书。

好吧,virtual是内置在c++中支持OOD的多态性(特别是运行时或动态的)的东西。

当您希望相同类型(Animal)的不同对象的行为不同(getProduceName()返回"猪肉"或"牛肉"或"鸡蛋"或…)取决于上下文(Animal实际上是PigCowChicken或…),您使该行为/函数virtual

通常一个好的OOD应该有良好的接口/实现分离。在c++中,这是通过继承和abstract类/方法来实现的。

所以实际上当你使getProduceName()多态/virtual你实际上是试图提取接口(Animal)从不同的实现(PigCowChicken或…),以保护您的客户端代码从不同的实现。这样,如果你必须支持一个新的实现,你不必改变客户端代码,只要它坚持接口Animal

所以要回答这个问题: 如果一个方法的对象在编译时没有创建,那么它必须是虚拟的吗?

方法是否为虚方法并不取决于对象何时创建(编译/运行时)。这取决于您希望如何设计应用程序。创建一个函数virtual可以帮助您从不同的实现中提取接口。如果某个函数(getOwnerName())在所有实现类({return ownerName;})中具有相同的实现,则不需要将其设为虚函数。

PS:在我看来,多态是一个好的OOD的副产品,你不会设计你的类来实现"运行时多态",然后当你有一个好的OO设计时展示运行时多态。

虚函数的一个重要用途是允许旧代码调用新代码

考虑这一点。函数接受一个Car对象作为参数。假设它执行了test_drive()、refuel()、calculate_ability()、getStoppingDistance()等操作。所有这些方法都取决于您传入的汽车类型。

但是每年都有新车问世。现在,在现实世界中,我们只需导入一个XML文件,其中包含构成所有汽车的属性。但是为了方便讨论,假设我们必须静态地执行此操作:每次发现新车时,我们都必须重新构建整个程序。如果我们的程序很大,这将是非常不方便的。在某种程度上,我们调用了相同的函数,但在某种程度上又不是,因为对象的类型不同。为什么我们必须重新编译?

虚函数来拯救!当汽车制造商发布新车时,他们同时发布声明新类型的头文件(它继承了带有虚函数的公共基类)和库(定义特定方法的对象文件)。我们不需要重新编译应用程序,而是重新链接到这个新库。因此,旧代码(我们的应用程序)能够调用新代码(汽车附带的新库)。

假设你有这样的代码:

Animal * a = new Pig();

指针a的静态类型Animal,动态类型Pig

现在假设我们调用

a->MakeASound();

如果MakeASound是虚拟的,则调用动态类型(Pig)的MakeASound方法。

如果不是,则调用静态类型的MakeASound (Animal),无论Pig是否覆盖MakeASound方法。