最好的餐饮设计网站建设,ppt代写平台,产品推广步骤,查询网站有没有备案一、多重继承#xff08;无虚函数覆盖#xff09;
下面#xff0c;再让我们来看看多重继承中的情况#xff0c;假设有下面这样一个类的继承关系。注意#xff1a;子类并没有覆盖父类的函数。 测试代码#xff1a;
class Base1
{
public: virtual void f() { cout …一、多重继承无虚函数覆盖
下面再让我们来看看多重继承中的情况假设有下面这样一个类的继承关系。注意子类并没有覆盖父类的函数。 测试代码
class Base1
{
public: virtual void f() { cout Base1::f endl; } //虚函数定义virtual void g() { cout Base1::g endl; }virtual void h() { cout Base1::h endl; }
};class Base2
{
public: virtual void f() { cout Base2::f endl; } //虚函数定义virtual void g() { cout Base2::g endl; }virtual void h() { cout Base2::h endl; }
};class Base3
{
public:virtual void f() { cout Base3::f endl; }virtual void g() { cout Base3::g endl; }virtual void h() { cout Base3::h endl; }
};class Derive :public Base1, public Base2, public Base3 //多继承的情况——无虚继承覆盖
{
public:virtual void f1() { cout Derive::f1 endl; } //虚函数定义virtual void g1() { cout Derive::g1 endl; }
};
对于子类实例中的虚函数表是下面这个样子 我们可以看到 每个父类都有自己的虚表。子类的成员函数被放到了第一个父类的表中。所谓的第一个父类是按照声明顺序来判断的这样做就是为了解决不同的父类类型的指针指向同一个子类实例而能够调用到实际的函数。 二、多重继承有虚函数覆盖
下面我们再来看看如果发生虚函数覆盖的情况。 测试代码
class Base1 {
public: virtual void f() { cout Base1::f endl; }virtual void g() { cout Base1::g endl; }virtual void h() { cout Base1::h endl; }
};class Base2 {
public: virtual void f() { cout Base2::f endl; }virtual void g() { cout Base2::g endl; }virtual void h() { cout Base2::h endl; }
};class Base3 {
public: virtual void f() { cout Base3::f endl; }virtual void g() { cout Base3::g endl; }virtual void h() { cout Base3::h endl; }
};class Derive : public Base1, public Base2, public Base3 {
public:virtual void f() { cout Derive::f endl; } //唯一一个覆盖的子类函数virtual void g1() { cout Derive::g1 endl; }
};
下图中我们在子类中覆盖了父类的f()函数。 下面是对于子类实例中的虚函数表的图
我们可以看见三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样我们就可以任一静态类型的父类来指向子类并调用子类的f()了。如
Derive d;
Base1 *b1 d;
Base2 *b2 d;
Base3 *b3 d;
b1-f(); //Derive::f()
b2-f(); //Derive::f()
b3-f(); //Derive::f()b1-g(); //Base1::g()
b2-g(); //Base2::g()
b3-g(); //Base3::g()
安全性
每次写C的文章总免不了要批判一下C。这篇文章也不例外。通过上面的讲述相信我们对虚函数表有一个比较细致的了解了。水可载舟亦可覆舟。下面让我们来看看我们可以用虚函数表来干点什么坏事吧。
1. 通过父类型的指针访问子类自己的虚函数
我们知道子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数但我们根本不可能使用下面的语句来调用子类的自有虚函数
Base1 *b1 new Derive();
b1-g1(); //编译出错
任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法所以这样的程序根本无法编译通过。但在运行时我们可以通过指针的方式访问虚函数表来达到违反C语义的行为。关于这方面的尝试通过阅读后面附录的代码相信你可以做到这一点 2. 访问non-public的虚函数
另外如果父类的虚函数是private或是protected的但这些非public的虚函数同样会存在于虚函数表中所以我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数这是很容易做到的。如
#includeiostream
using namespace std;class Base {
private:virtual void f() { cout Base::f endl; }
};class Derive : public Base {};typedef void(*Fun)(void);int main()
{Derive d;Fun pFun (Fun)*((int*)*(int*)(d) 0);pFun();
} 输出结果 三、多重继承
下面再让我们来看看多重继承中的情况假设有下面这样一个类的继承关系。
注意子类只overwrite了父类的f()函数而还有一个是自己的函数我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表。而且每个类中都有一个自己的成员变量 们的类继承的源代码如下所示父类的成员初始为102030子类的为100
#includeiostream
using namespace std;class Base1 {
public:int ibase1;Base1() :ibase1(10) {}virtual void f() { cout Base1::f() endl; }virtual void g() { cout Base1::g() endl; }virtual void h() { cout Base1::h() endl; }};class Base2 {
public:int ibase2;Base2() :ibase2(20) {}virtual void f() { cout Base2::f() endl; }virtual void g() { cout Base2::g() endl; }virtual void h() { cout Base2::h() endl; }
};class Base3 {
public:int ibase3;Base3() :ibase3(30) {}virtual void f() { cout Base3::f() endl; }virtual void g() { cout Base3::g() endl; }virtual void h() { cout Base3::h() endl; }
};class Derive : public Base1, public Base2, public Base3 {
public:int iderive;Derive() :iderive(100) {}virtual void f() { cout Derive::f() endl; }virtual void g1() { cout Derive::g1() endl; }
};int main()
{typedef void(*Fun)(void);Derive d;int** pVtab (int**)d;cout [0] Base1::_vptr- endl;Fun pFun (Fun)pVtab[0][0];cout [0] ;pFun();pFun (Fun)pVtab[0][1];cout [1] ; pFun();pFun (Fun)pVtab[0][2];cout [2] ; pFun();pFun (Fun)pVtab[0][3];cout [3] ; pFun();pFun (Fun)pVtab[0][4];cout [4] ; cout pFun endl;cout [1] Base1.ibase1 (int)pVtab[1] endl;int s sizeof(Base1) / 4;cout [ s ] Base2::_vptr- endl;pFun (Fun)pVtab[s][0];cout [0] ; pFun();pFun (Fun)pVtab[s][1];cout [1] ; pFun();pFun (Fun)pVtab[s][2];cout [2] ; pFun();pFun (Fun)pVtab[s][3];cout [3] ;cout pFun endl;cout [ s 1 ] Base2.ibase2 (int)pVtab[s 1] endl;s s sizeof(Base2) / 4;cout [ s ] Base3::_vptr- endl;pFun (Fun)pVtab[s][0];cout [0] ; pFun();pFun (Fun)pVtab[s][1];cout [1] ; pFun();pFun (Fun)pVtab[s][2];cout [2] ; pFun();pFun (Fun)pVtab[s][3];cout [3] ;cout pFun endl;s;cout [ s ] Base3.ibase3 (int)pVtab[s] endl;s;cout [ s ] Derive.iderive (int)pVtab[s] endl;
}
输出结果 使用图片表示是下面这个样子 我们可以看到 每个父类都有自己的虚表。子类的成员函数被放到了第一个父类的表中。内存布局中其父类布局依次按声明顺序排列。每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例而能够调用到实际的函数。四、重复继承
面我们再来看看发生重复继承的情况。所谓重复继承也就是某个基类被间接地重复继承了多次。
下图是一个继承图我们重载了父类的f()函数。其类继承的源代码如下所示。其中每个类都有两个变量一个是整形4字节一个是字符1字节而且还有自己的虚函数自己overwrite父类的虚函数。如子类D中f()覆盖了超类的函数 f1() 和f2() 覆盖了其父类的虚函数Df()为自己的虚函数。 测试代码
#includeiostream
using namespace std;class B {
public:int ib;char cb;
public:B() : ib(0), cb(B) {}virtual void f() { cout B::f() endl; }virtual void Bf() { cout B::Bf() endl; }
};class B1 : public B {
public:int ib1;char cb1;
public:B1() : ib1(11), cb1(1) {}virtual void f() { cout B1::f() endl; }virtual void f1() { cout B1::f1() endl; }virtual void Bf1() { cout B1::Bf1() endl; }
};class B2 : public B {
public:int ib2;char cb2;
public:B2() :ib2(12), cb2(2) {}virtual void f() { cout B2::f() endl; }virtual void f2() { cout B2::f2() endl; }virtual void Bf2() { cout B2::Bf2() endl; }};class D : public B1, public B2 {
public:int id;char cd;
public:D() : id(100), cd(D) {}virtual void f() { cout D::f() endl; }virtual void f1() { cout D::f1() endl; }virtual void f2() { cout D::f2() endl; }virtual void Df() { cout D::Df() endl; }};int main()
{typedef void(*Fun)(void);int** pVtab NULL;Fun pFun NULL;D d;pVtab (int**)d;cout [0] D::B1::_vptr- endl;pFun (Fun)pVtab[0][0];cout [0] ; pFun();pFun (Fun)pVtab[0][1];cout [1] ; pFun();pFun (Fun)pVtab[0][2];cout [2] ; pFun();pFun (Fun)pVtab[0][3];cout [3] ; pFun();pFun (Fun)pVtab[0][4];cout [4] ; pFun();pFun (Fun)pVtab[0][5];cout [5] 0x pFun endl;cout [1] B::ib (int)pVtab[1] endl;cout [2] B::cb (char)pVtab[2] endl;cout [3] B1::ib1 (int)pVtab[3] endl;cout [4] B1::cb1 (char)pVtab[4] endl;cout [5] D::B2::_vptr- endl;pFun (Fun)pVtab[5][0];cout [0] ; pFun();pFun (Fun)pVtab[5][1];cout [1] ; pFun();pFun (Fun)pVtab[5][2];cout [2] ; pFun();pFun (Fun)pVtab[5][3];cout [3] ; pFun();pFun (Fun)pVtab[5][4];cout [4] 0x pFun endl;cout [6] B::ib (int)pVtab[6] endl;cout [7] B::cb (char)pVtab[7] endl;cout [8] B2::ib2 (int)pVtab[8] endl;cout [9] B2::cb2 (char)pVtab[9] endl;cout [10] D::id (int)pVtab[10] endl;cout [11] D::cd (char)pVtab[11] endl;
}
输出结果 下面是对于子类实例中的虚函数表的图(第一份图为原作者的图第二幅图为修改的图) 我们可以看见最顶端的父类B其成员变量存在于B1和B2中并被D给继承下去了。而在D中其有B1和B2的实例于是B的成员在D的实例中存在两份一份是B1继承而来的另一份是B2继承而来的。所以如果我们使用以下语句则会产生二义性编译错误 1 2 3 4 D d; d.ib 0; //二义性错误 d.B1::ib 1; //正确 d.B2::ib 2; //正确
注意上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误但B类在D中还是有两个实例这种继承造成了数据的重复我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以C引入了虚基类的概念。 参考资料
C 虚函数表解析 陈皓著C 对象的内存布局 陈皓著