#include<iostream>
using namespace std;
int main(){
class c1{
public:
int func(){
cout<<"in the c1";
}
};
class c2:public c1{
public:
int func(){
cout<<" in c2";
}
};
c1* a;
c2 b;
a=&b;
a->func();
}
我知道我应该使用虚函数来获得所需的结果,但我想知道上面的代码是怎么回事。为什么调用c1 :: func()而不是c2 :: FUNC()?
另请注意使用virtual
时与本案不同的情况。
答案 0 :(得分:4)
当成员函数不是virtual
时,调用的函数仅由点(.
)或箭头(->
)运算符左侧的表达式类型决定。这称为“静态类型”。
当成员函数为virtual
时,调用的函数由点的左侧表达式(.
)指定的或由左侧表达式指向的对象的实际派生类型确定箭头(->
)。这被称为“动态类型”。
请注意,当用于点左侧的变量,成员,参数或返回类型具有普通类类型时,静态类型和动态类型始终相同。但是,如果变量,成员,参数或返回类型是类类型的指针或引用,则静态类型和动态类型可以不同。
答案 1 :(得分:1)
见http://www.cs.technion.ac.il/users/yechiel/c++-faq/dyn-binding.html,我引用:
静态解析非虚拟成员函数。也就是说,基于对象的指针(或引用)的类型,静态地(在编译时)选择成员函数。
相反,虚拟成员函数是动态解析的(在运行时)。也就是说,基于对象的类型动态地(在运行时)选择成员函数,而不是指向该对象的指针/引用的类型。这称为“动态绑定”。大多数编译器使用以下技术的一些变体:如果对象具有一个或多个虚函数,则编译器会在对象中放置一个名为“虚拟指针”或“v指针”的隐藏指针。此v指针指向名为“virtual-table”或“v-table”的全局表。
答案 2 :(得分:0)
查看以C风格实现的虚拟和非虚函数可能会有所帮助。
struct bob {
int x;
static int get_x_1( bob* self ){ return self->x; }
int get_x_2() { return this->x; }
};
inline int get_x_3( bob* self ){ return self->x; }
以上所有3个基本上是相同的东西(调用约定的小细节 - 寄存器或堆栈位置参数继续 - 可能不同)。
非虚拟成员函数只是采用名为this
的半秘密指针的函数。半秘密,因为它就在方法名称的左侧。
这一点很重要。非虚拟调用只是花哨的函数调用。该类的实例不存储指向其方法的指针,或类似的东西。它们只是单调函数调用的语法糖。
现在虚拟成员函数不同。
以下是我们如何实现这一点:
class bob{
public:
virtual int get_x(){ return x; }
int x;
};
不使用virtual
:
struct bob;
using get_x=int(bob*);
struct bob_vtable {
get_x* get_x=0;
};
inline int get_x_impl( bob* self );
bob_vtable* get_bob_vtable(){
static bob_vtable vtable{ get_x_impl };
return &vtable;
}
struct bob {
bob_vtable* vtable=0;
int x;
int get_x(){ return this->vtable->get_x(this); }
bob(): vtable(get_bob_vtable()) {}
};
inline int get_x_impl( bob* self ){ return self->x; }
很多事情正在发生。
首先我们有get_x_impl
,这与上面的非虚拟get_x
非常相似。
Sexond,我们有一个函数指针表(这里只有一个)称为vtable。我们的bob
包含指向vtable的指针作为其第一个条目。在其中我们有一个指向我们get_x_impl
的函数指针。最后,bob
有一个get_x
方法,可以通过vtable将调用通过函数指针转发到get_x_impl
。
在构造过程中,派生类型可以更改vtable指针以指向不同的函数表,并使用get_x
的不同实现。
然后当ypu有一个指向bob
的指针并调用get_x
时,它将跟随更改的vtable中的指针并调用替换实现。
创建虚拟功能时,会为您编写上述机器。当你继承和覆盖时,用派生替换父vtable指针的代码被注入到派生类型的构造函数中。
所有这些基本上都是在C ++存在之前实现C中人们可以做的事情。他们只是隐藏了细节,并用C ++编写了胶水代码。