c++;虚拟关键字vs覆盖函数 我正在学习C++,并且正在学习虚拟关键词。我在互联网上搜索,试图理解它,但没有用。我进入我的编辑器并做了以下实验,希望它能打印出两次基本消息(因为我觉得需要使用virtual关键字来覆盖函数)。然而,它打印出了两条不同的信息。如果我们可以简单地重写函数,并且仍然获得多态行为,有人能解释为什么我们需要virtual关键字吗?也许将来有人能帮助我和其他人理解虚拟与重写。(我得到的输出是“我是基础”,后面是“我是派生的”) #包括 使用名称空间std; 阶级基础{ 公众: void printMe(){ cout

c++;虚拟关键字vs覆盖函数 我正在学习C++,并且正在学习虚拟关键词。我在互联网上搜索,试图理解它,但没有用。我进入我的编辑器并做了以下实验,希望它能打印出两次基本消息(因为我觉得需要使用virtual关键字来覆盖函数)。然而,它打印出了两条不同的信息。如果我们可以简单地重写函数,并且仍然获得多态行为,有人能解释为什么我们需要virtual关键字吗?也许将来有人能帮助我和其他人理解虚拟与重写。(我得到的输出是“我是基础”,后面是“我是派生的”) #包括 使用名称空间std; 阶级基础{ 公众: void printMe(){ cout,c++,polymorphism,overriding,virtual,C++,Polymorphism,Overriding,Virtual,您没有看到此处的行为,因为您已声明b为派生类型,因此编译器知道要使用哪些函数。为了揭示为什么需要使用虚拟,您需要混合以下内容: int main() { Base a; Base *b = new Derived(); a.printMe(); b->printMe(); delete b; return 0; } 现在b属于Base*类型,这意味着它将使用Base上的函数以及虚拟函数表中的任何函数。这破坏了您的实现。如果您这样做,您

您没有看到此处的行为,因为您已声明
b
派生类型
,因此编译器知道要使用哪些函数。为了揭示为什么需要使用
虚拟
,您需要混合以下内容:

int main() {
    Base a;
    Base *b = new Derived();

    a.printMe();
    b->printMe();

    delete b;

    return 0;
}

现在
b
属于
Base*
类型,这意味着它将使用
Base
上的函数以及虚拟函数表中的任何函数。这破坏了您的实现。如果您这样做,您可以通过正确地用您拥有的代码声明事物来修复它

Derived derived;
Base* base_ptr = &derived;
base_ptr->printMe();

您认为会发生什么情况?它不会打印出
I是派生的
,因为该方法不是虚拟的,并且分派是在调用对象的静态类型之外完成的(即
Base
)。如果将其更改为虚拟,则调用的方法将取决于对象的动态类型,而不是静态类型。

考虑以下示例。说明需要
virtual
override
的重要一行是
c->printMe()
。请注意,
c
的类型是
Base*
,但是由于多态性,它能够正确地从派生类调用重写的方法。
override
关键字允许编译器强制派生类方法与标记为
virtual
的基类方法的签名相匹配。如果将
override
关键字添加到派生类函数中,则该函数不需要派生类中的
virtual
关键字,因为虚拟类是隐含的

#include <iostream>

class Base{
public:
    virtual void printMe(){
        std::cout << "I am the base" << std::endl;
    }
};

class Derived: public Base{
public:
    void printMe() override {
        std::cout << "I am the derived" << std::endl;
    }
};

int main() {
    Base a;
    Derived b;
    a.printMe();
    b.printMe();
    Base* c = &b;
    c->printMe();
    return 0;
}

override
是C++11中添加的一个新关键字

您应该使用它,因为:

  • 编译器将检查基类是否包含匹配的
    virtual
    方法。这一点很重要,因为方法名称或其参数列表中的某些输入错误(允许重载)可能会给人留下这样的印象,即某些内容实际上没有被覆盖,但却被覆盖了

  • 如果对一个方法使用
    override
    ,如果在不使用
    override
    关键字的情况下重写另一个方法,编译器将报告错误。这有助于在发生符号冲突时检测不需要的重写

  • virtual
    并不意味着“override”。在类中,不要使用“override”关键字,而要重写一个方法,您只需编写此方法,省去“virtual”关键字,重写将隐式发生。开发人员在C++11之前编写了
    virtual
    ,以表明他们重写的意图。简单地说
    virtual
    意味着:此方法可以在子类中重写


    • 我想你的问题是,为什么有人会在程序中使用基类指针来调用派生类

      一种情况是,您希望程序中的所有派生类都有一个公共函数。您不希望使用不同的派生类类型参数创建相同的函数。 见下文

      #include<iostream>
      using namespace std;
      
      class Base{
      public:
          virtual void printfunc() { cout<<"this is base class";};
      };
      class Derived:public Base{
      public:
          void printfunc(){cout<<"this is derived class";};
      };
      
      void printthis(Base *ptr)
      {
          ptr->printfunc();
      }
      
      int main()
      {
          Derived func;
              printthis(&func);
          return 0;
      }
      
      #包括
      使用名称空间std;
      阶级基础{
      公众:
      
      virtual void printfunc(){cout
      virtual
      的意思是,“这不是一个真正的C函数,即一系列的参数推送到堆栈上,然后跳转到函数体的一个不变地址。”

      相反,是另一个beast在运行时在表中查找要执行的函数体的地址。层次结构中的每个类在该表中都有一个条目。函数指针表称为vtable。这是多态性的运行时机制,注入额外代码来执行此查找,然后分派到相应的sp函数体的专用版本

      此外,在使用此vtable分派机制时,始终通过指向对象的指针访问对象,而不是直接访问(变量或引用),即
      Foo*Foo{makeFoo()};Foo->someMethod()
      vs.
      Loo-Loo{};Loo.someMethod()
      。因此,要使用此技术,需要在开始时进行另一次取消引用

      这里有一个巧妙的部分:这些指针也可以指向派生类的任何对象,因此如果您有一个从
      foopparent
      继承的类
      FooChild
      ,您可以使用
      FoodParent*
      指向
      foopparent
      FooChild

      调用该方法时,不只是执行常规的C操作,即准备堆栈上的参数,然后跳转到
      barMethod()的主体
      ,它首先执行一系列运行时工作,查找每个类个性化的几种不同barMethod实现中的一种。该表称为vtable。类层次结构中的每个类在此表中都有一个条目,说明该特定类的函数体的实际位置,因为它们可以有不同的函数体,例如即使我们使用
      FooParent*
      指向其中任何一个的实例

      但这就是为什么我们首先要这么做:假设
      虚拟
      不存在。而你,程序员,想要处理一堆来自类层次结构的对象。那么,你最终会编写与
      I am the base
      I am the derived
      I am the derived
      
      #include<iostream>
      using namespace std;
      
      class Base{
      public:
          virtual void printfunc() { cout<<"this is base class";};
      };
      class Derived:public Base{
      public:
          void printfunc(){cout<<"this is derived class";};
      };
      
      void printthis(Base *ptr)
      {
          ptr->printfunc();
      }
      
      int main()
      {
          Derived func;
              printthis(&func);
          return 0;
      }
      
      
      struct FooParent
      {
          // The root has virtual
          virtual void barMethod(); // `= 0;`, potentially as well for "pure virtual," i.e. undefined in the root
          {//some code...}
      }
      
      // Original way of doing it. Just use virtual again, but this isn't the root anymore.
      struct FooChild_OldSchool : FooParent
      {
          virtual void barMethod(); // Total trashmouth.
      }
      
      struct FooChild_OverrideDays : FooParent
      {
         virtual void barMethod() override; // Naughty mouth.
      }
      
      struct FooChild_NonTrashyWay2020 : FooParent
      {
        void barMethod() override; // Prim and proper mouth speaking a bizarre langauge.
      }
      
      struct FooChild_HowIWishItWas : FooParent
      {
        override void barMethod();
      }
      
      // OR EVEN BETTER! Allow us to change the location virtual!
      struct FooParent_HowIWishItWasEvenMore
      {
         void barMethod() virtual;
      }