如何调用虚拟和非虚函数

时间:2015-06-30 12:14:37

标签: c++ virtual-functions

using namespace std;

class Foo
{
    public:
        virtual void foo();
        void foo2();
};
class Bar : public Foo
{
    public:
        void foo();
        void foo2();
};
int main()
{
    Foo* f = new Foo;
    f->foo();         **//1**
    f->foo2();       **//2**
    return 0;
}

编译器如何知道,1)本质上是动态的,2)是静态的。 如何在内部调用它们。

4 个答案:

答案 0 :(得分:3)

Collected from here . . .

静态解析非虚拟成员函数。也就是说,基于对象的指针(或引用)的类型,静态地(在编译时)选择成员函数。

相反,虚拟成员函数是动态解析的(在运行时)。也就是说,基于对象的类型动态地(在运行时)选择成员函数,而不是指向该对象的指针/引用的类型。这称为“动态绑定”。大多数编译器使用以下技术的一些变体:如果对象具有一个或多个虚函数,则编译器会在对象中放置一个名为“虚指针”或“v指针”的隐藏指针。 “这个v指针指向一个名为”virtual-table“或”v-table“的全局表。

纯虚函数是必须在派生类中重写且无需定义的函数。使用curious = 0语法将虚函数声明为“纯”。例如:

class Base {
public:
    void f1();      // not virtual
    virtual void f2();  // virtual, not pure
    virtual void f3() = 0;  // pure virtual
};
Base b; // error: pure virtual f3 not overridden

这里,Base是一个抽象类(因为它有一个纯虚函数),所以不能直接创建Base类的对象:Base(显式)意味着是一个基类。例如:

class Derived : public Base {
    // no f1: fine
    // no f2: fine, we inherit Base::f2
    void f3();
};
Derived d;  // ok: Derived::f3 overrides Base::f3

虚拟或非虚拟分区的示例

#include <iostream>
using namespace std;

class Base {
    public:
          virtual void NameOf();   // Virtual function.
          void InvokingClass();   //  Nonvirtual function.
          };

// Implement the two functions.
void Base::NameOf() {
    cout << "Base::NameOf\n";
   }

void Base::InvokingClass() {
    cout << "Invoked by Base\n";
   }

class Derived : public Base {
    public:
          void NameOf();   // *Virtual function*.
          void InvokingClass();   // *Nonvirtual function.*
  };

// Implement the two functions.
void Derived::NameOf() {
    cout << "Derived::NameOf\n";
 }

void Derived::InvokingClass() {
   cout << "Invoked by Derived\n";
 }

int main() {
   // Declare an object of type Derived.
      Derived aDerived;

   // Declare two pointers, one of type Derived * and the other
   //  of type Base *, and initialize them to point to aDerived.
      Derived *pDerived = &aDerived;
      Base    *pBase    = &aDerived;

  // Call the functions.
     pBase->NameOf();           // Call virtual function.
     pBase->InvokingClass();    // Call nonvirtual function.
     pDerived->NameOf();        // Call virtual function.
     pDerived->InvokingClass(); // Call nonvirtual function.
}

答案 1 :(得分:0)

在您的示例中,foo()foo2()都来自Foo类。

int main()
{
    Foo* f = new Foo;
    f->foo();                        // Foo::foo
    f->foo2();                       // Foo::foo2
    return 0;
}

为了说明virtual行为,您需要创建派生类的实例

int main()
{
    Foo* b = new Bar;
    b->foo();                        // Bar::foo
    b->foo2();                       // Foo::foo2
    static_cast<Bar*>(b)->foo2();    // Bar::foo2
    return 0;
}

请注意,在后一种情况下,由于b实际上是Bar,因此它会调用覆盖虚拟方法foo。但由于foo2未声明为virtualbFoo,因此会调用Foo::foo2。但是,如果我们将f转换为Bar,则会调用Bar::foo2

答案 2 :(得分:0)

virtual keyword告诉编译器动态绑定。

要在动作中查看动态绑定,请使用Bar对象实例化Foo指针,请参阅下面的代码。

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

class Foo
{
    public:
        virtual void foo(){std::cout<<"Foo foo"<<std::endl;};
        void foo2(){std::cout<<"Foo foo2"<<std::endl;};
};
class Bar : public Foo
{
    public:
        void foo(){std::cout<<"Bar foo"<<std::endl;};
        void foo2(){std::cout<<"Bar foo2"<<std::endl;};
};
int main()
{
    Foo* f = new Bar;
    f->foo();
    f->foo2();
    return 0;
}

答案 3 :(得分:0)

声明或继承虚函数的类有一个叫做 vtable 的东西,用于在调用虚函数时查找要调用的函数。实际上,这个表包含指向类中所有虚函数的指针,类似这样(伪代码 - 这可能编译也可能不编译):

class Foo {
    void foo_impl(){std::cout<<"Foo foo"<<std::endl;}
    struct {
        void (*foo_ptr)();
    } vtable;
    public:
        Foo(){vtable.foo_ptr = &Foo::foo_impl;}
        void foo(){vtable.foo_ptr();}
        void foo2(){std::cout<<"Foo foo2"<<std::endl;}
};
class Bar : public Foo {
    void foo_impl(){std::cout<<"Bar foo"<<std::endl;}
    public:
        Bar(){vtable.foo_ptr = &Bar::foo_impl;}
        void foo2(){std::cout<<"Bar foo2"<<std::endl;}
};

因此,当您调用虚拟函数时,首先会在 vtable 中查找该地址,因此如果您分配Bar bar; Foo& foo = bar;,则foo.foo()调用{{1} {}}的版本Bar而不是foo()的版本。