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)是静态的。 如何在内部调用它们。
答案 0 :(得分:3)
静态解析非虚拟成员函数。也就是说,基于对象的指针(或引用)的类型,静态地(在编译时)选择成员函数。
相反,虚拟成员函数是动态解析的(在运行时)。也就是说,基于对象的类型动态地(在运行时)选择成员函数,而不是指向该对象的指针/引用的类型。这称为“动态绑定”。大多数编译器使用以下技术的一些变体:如果对象具有一个或多个虚函数,则编译器会在对象中放置一个名为“虚指针”或“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
未声明为virtual
且b
为Foo
,因此会调用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()
的版本。