我正在学习C++,我刚刚开始学习虚拟函数。

从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。

但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。

那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。


当前回答

底线是,虚拟功能使生活更轻松。让我们使用M Perry的一些想法,并描述如果我们没有虚拟函数而只能使用成员函数指针会发生什么。在没有虚函数的正常估计中,我们有:

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
  };

 class derived: public base {
 public:
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main () {
      base hwOne;
      derived hwTwo = new derived();
      base->helloWorld(); //prints "Hello World!"
      derived->helloWorld(); //prints "Hello World!"

好的,这就是我们所知道的。现在让我们尝试使用成员函数指针:

 #include <iostream>
 using namespace std;

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
 };

 class derived : public base {
 public:
 void displayHWDerived(void(derived::*hwbase)()) { (this->*hwbase)(); }
 void(derived::*hwBase)();
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main()
 {
 base* b = new base(); //Create base object
 b->helloWorld(); // Hello World!
 void(derived::*hwBase)() = &derived::helloWorld; //create derived member 
 function pointer to base function
 derived* d = new derived(); //Create derived object. 
 d->displayHWDerived(hwBase); //Greetings World!

 char ch;
 cin >> ch;
 }

虽然我们可以用成员函数指针做一些事情,但它们不如虚拟函数灵活。在类中使用成员函数指针是很棘手的;至少在我的实践中,成员函数指针几乎总是必须在主函数中或从成员函数中调用,如上面的示例所示。

另一方面,虚拟函数虽然可能有一些函数指针开销,但确实大大简化了事情。

EDIT:还有一种方法与eddietree类似:c++虚拟函数与成员函数指针(性能比较)。

其他回答

您必须区分重写和重载。如果没有virtual关键字,则只能重载基类的方法。这只意味着隐藏。假设您有一个基类base和一个派生类Specialized,它们都实现了void foo()。现在有一个指向Base的指针指向Specialized的实例。当您对其调用foo()时,您可以观察到virtual的不同之处:如果该方法是虚拟的,则将使用Specialized的实现,如果缺少,则将选择Base的版本。最好不要重载基类中的方法。使方法非虚拟化是作者告诉你它在子类中的扩展不是有意的。

当基类中有函数时,可以在派生类中重新定义或重写它。

重新定义方法:派生类中给出了基类方法的新实现。不便于动态绑定。

重写方法:在派生类中重新定义基类的虚拟方法。虚拟方法有助于动态绑定。

所以当你说:

但在书的早些时候,当我了解基本遗传时能够重写派生类中的基方法,而不使用“虚拟”。

因为基类中的方法不是虚拟的,所以您不是在重写它,而是在重新定义它

我以对话的形式给出了答案,以便更好地阅读:


为什么我们需要虚拟功能?

因为多态性。

什么是多态性?

基指针也可以指向派生类型对象。

多态性的定义是如何导致对虚拟函数的需求的?

嗯,通过早期绑定。

什么是早期绑定?

C++中的早期绑定(编译时绑定)意味着在执行程序之前,函数调用是固定的。

所以

因此,如果您使用基类型作为函数的参数,编译器将只识别基接口,如果您用派生类的任何参数调用该函数,它将被截断,这不是您想要的。

如果这不是我们想要的,为什么允许这样做?

因为我们需要多态性!

那么多态性的好处是什么?

您可以使用基类型指针作为单个函数的参数,然后在程序运行时,您可以使用该基指针的解引用来访问每个派生类型接口(例如,它们的成员函数),而不会出现任何问题。

我仍然不知道虚拟函数有什么好处。。。!这是我的第一个问题!

嗯,这是因为你问得太快了!

为什么我们需要虚拟功能?

假设您使用基指针调用了一个函数,该函数具有来自其派生类之一的对象地址。正如我们上面所讨论的,在运行时,这个指针会被取消引用,但是,到目前为止,我们希望“从我们的派生类”执行一个方法(==成员函数)!然而,基类中已经定义了相同的方法(具有相同标头的方法),那么为什么您的程序要费心选择另一个方法呢?换言之,我的意思是,你怎么能把这种情况与我们以前通常看到的情况区分开来?

简单的答案是“基类中的一个虚拟成员函数”,稍长一点的答案是,“在这一步,如果程序在基类中看到一个虚拟函数,它知道(意识到)您正在尝试使用多态性”,因此去到派生类(使用v-table,一种后期绑定形式),发现另一个方法具有相同的头,但预期实现不同。

为什么实施不同?

你这个笨蛋!去读一本好书吧!

好吧,等等,等等,当他/她可以简单地使用派生类型指针时,为什么还要麻烦使用基指针呢?你是法官,所有这些头疼值得吗?看看这两个片段:

//1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

//2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

好吧,虽然我认为1还是比2好,但你也可以这样写1:

//1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

此外,你应该意识到,这只是我迄今为止向你解释的所有事情的一种人为使用。相反,假设例如在程序中有一个函数分别使用每个派生类的方法(getMonthBenefit()):

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

现在,试着重新写一遍,不要让人头疼!

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

事实上,这可能也是一个虚构的例子!

底线是,虚拟功能使生活更轻松。让我们使用M Perry的一些想法,并描述如果我们没有虚拟函数而只能使用成员函数指针会发生什么。在没有虚函数的正常估计中,我们有:

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
  };

 class derived: public base {
 public:
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main () {
      base hwOne;
      derived hwTwo = new derived();
      base->helloWorld(); //prints "Hello World!"
      derived->helloWorld(); //prints "Hello World!"

好的,这就是我们所知道的。现在让我们尝试使用成员函数指针:

 #include <iostream>
 using namespace std;

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
 };

 class derived : public base {
 public:
 void displayHWDerived(void(derived::*hwbase)()) { (this->*hwbase)(); }
 void(derived::*hwBase)();
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main()
 {
 base* b = new base(); //Create base object
 b->helloWorld(); // Hello World!
 void(derived::*hwBase)() = &derived::helloWorld; //create derived member 
 function pointer to base function
 derived* d = new derived(); //Create derived object. 
 d->displayHWDerived(hwBase); //Greetings World!

 char ch;
 cin >> ch;
 }

虽然我们可以用成员函数指针做一些事情,但它们不如虚拟函数灵活。在类中使用成员函数指针是很棘手的;至少在我的实践中,成员函数指针几乎总是必须在主函数中或从成员函数中调用,如上面的示例所示。

另一方面,虚拟函数虽然可能有一些函数指针开销,但确实大大简化了事情。

EDIT:还有一种方法与eddietree类似:c++虚拟函数与成员函数指针(性能比较)。

如果基类是base,派生类是Der,则可以有一个base*p指针,它实际上指向Der的实例。当您调用p->foo();时;,如果foo不是虚拟的,则执行Base版本的foo,忽略p实际上指向Der的事实。如果foo是虚拟的,则p->foo()执行foo的“最叶”覆盖,充分考虑指向项的实际类。因此,虚拟和非虚拟之间的区别实际上非常关键:前者允许运行时多态性,这是OO编程的核心概念,而后者则不允许。