程序员——有关虚函数的一个问题
第一节、一道简单的虚函数的面试题
题目要求:写出下面程序的运行结果?
view plaincopy to clipboardprint?
#include <iostream>
using namespace std;
class A
{
public:
virtual void p()
{
cout << "A" << endl;
}
};
class B : public A
{
public:
virtual void p()
{
cout << "B" << endl;
}
};
int main()
{
A * a = new A;
A * b = new B;
a->p();
b->p();
delete a;
delete b;
return 0;
}
#include <iostream>
using namespace std;class A
{
public:
virtual void p()
{
cout << "A" << endl;
}
};class B : public A
{
public:
virtual void p()
{
cout << "B" << endl;
}
};int main()
{
A * a = new A;
A * b = new B;
a->p();
b->p();
delete a;
delete b;
return 0;
}我想,这道面试题应该是考察虚函数相关知识的相对简单的一道题目了。然后,希望你碰到此类有关虚函数的面试题,不论其难度是难是易,都能够举一反三,那么本章的目的也就达到了。ok,请跟着我的思路,咱们步步深入(上面程序的输出结果为A B)。
第二节、有无虚函数的区别
1、当上述程序中的函数p()不是虚函数,那么程序的运行结果是如何?即如下代码所示:class A
{
public:
void p()
{
cout << "A" << endl;
}
};class B : public A
{
public:
void p()
{
cout << "B" << endl;
}
};对的,程序此时将输出两个A,A。为什么?
我们知道,在构造一个类的对象时,如果它有基类,那么首先将构造基类的对象,然后才构造派生类自己的对象。如上,A* a=new A,调用默认构造函数构造基类A对象,然后调用函数p(),a->p();输出A,这点没有问题。
然后,A * b = new B;,构造了派生类对象B,B由于是基础基类A的派生类对象,所以会先构造基类A对象,然后再构造派生类对象,但由于当程序中函数是非虚函数调用时,B类对象对函数p()的调用时在编译时就已静态确定了,所以,不论基类指针b最终指向的是基类对象还是派生类对象,只要后面的对象调用的函数不是虚函数,那么就直接无视,而调用基类A的p()函数。2、那如果加上虚函数呢?即如上面的程序那样,那么,程序的输出结果,将是什么?
在此之前,我们还得明确以下两点:
a、通过基类引用或指针调用基类中定义的函数时,我们并不知道执行函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类型的。
b、如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数(如上述第1点所述)。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本。根据上述b的观点,我们知道,如果加上虚函数,如上面这道面试题,
class A
{
public:
virtual void p()
{
cout << "A" << endl;
}
};class B : public A
{
public:
virtual void p()
{
cout << "B" << endl;
}
};int main()
{
A * a = new A;
A * b = new B;
a->p();
b->p();
delete a;
delete b;
return 0;
}那么程序的输出结果将是A B。
所以,至此,咱们的这道面试题已经解决。但虚函数的问题,还没有解决。
第三节、虚函数的原理与本质
我们已经知道,虚(virtual)函数的一般实现模型是:每一个类(class)有一个虚表(virtual table),内含该class之中有作用的虚(virtual)函数的地址,然后每个对象有一个vptr,指向虚表(virtual table)的所在。请允许我援引自深度探索c++对象模型一书上的一个例子:
class Point {
public:
virtual ~Point();virtual Point& mult( float ) = 0;
float x() const { return _x; } //非虚函数,不作存储
virtual float y() const { return 0; }
virtual float z() const { return 0; }
// ...protected:
Point( float x = 0.0 );
float _x;
};1、在Point的对象pt中,有两个东西,一个是数据成员_x,一个是_vptr_Point。其中_vptr_Point指向着virtual table point,而这个virtual table point则存储着5个东西,如上述程序中注释所说明的那般:在上述图所示的virtual table(虚表)的布局中:
virtual ~Point()被赋值slot 1,
mult() 将被赋值slot 2.
y() is 将被赋值slot 3
z() 将被赋值slot 4.
class Point2d : public Point {
public:
Point2d( float x = 0.0, float y = 0.0 )
: Point( x ), _y( y ) {}
~Point2d(); //1//改写base class virtual functions
Point2d& mult( float ); //2
float y() const { return _y; } //3protected:
float _y;
};2、在Point2d的对象pt2d中,也有两个东西,一个是数据成员_y,一个是_vptr_Point。其中_vptr_Point指向着virtual table point2d。由于Point2d继承自Point,所以在virtual table point2d中存储着:改写了的其中的~Point2d()、Point2d& mult( float )、float y() const,以及未被改写的Point::z()函数。
class Point3d: public Point2d {
public:
Point3d( float x = 0.0,
float y = 0.0, float z = 0.0 )
: Point2d( x, y ), _z( z ) {}
~Point3d();// overridden base class virtual functions
Point3d& mult( float );
float z() const { return _z; }// ... other operations ...
protected:
float _z;
};3、理解可按与上述1、2情况的分析,具体不再赘述。ok,上述一切的详情,请参考下图。
(图:virtual table(虚表)的布局:单一继承情况)
补充:软件开发 , C语言 ,