存储

一、多重继承(无虚函数覆盖)

 

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

 

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()了。如:

 1 Derive d;2 Base1 *b1 = &d;3 Base2 *b2 = &d;4 Base3 *b3 = &d;5 b1->f(); //Derive::f()6 b2->f(); //Derive::f()7 b3->f(); //Derive::f()8  9 b1->g(); //Base1::g()
10 b2->g(); //Base2::g()
11 b3->g(); //Base3::g()

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

1. 通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

Base1 *b1 = new Derive();
b1->g1();  //编译出错

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

 

2. 访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。如

#include<iostream>
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()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:

们的类继承的源代码如下所示:父类的成员初始为10,20,30,子类的为100

#include<iostream>
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()为自己的虚函数。

#include<iostream>
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++引入了虚基类的概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/385194.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BTree和B+Tree详解

B 树是为了磁盘或其它存储设备而设计的一种多叉&#xff08;下面你会看到&#xff0c;相对于二叉&#xff0c;B树每个内结点有多个分支&#xff0c;即多叉&#xff09;平衡查找树。 B 树又叫平衡多路查找树。一棵m阶的B 树 (m叉树)的特性如下&#xff1a; 树中每个结点最多含…

【1】MySQL的四种事务隔离级别

二、事务的并发问题 1、脏读&#xff1a;事务A读取了事务B更新的数据&#xff0c;然后B回滚操作&#xff0c;那么A读取到的数据是脏数据 2、不可重复读&#xff1a;事务 A 多次读取同一数据&#xff0c;事务 B 在事务A多次读取的过程中&#xff0c;对数据作了更新并提交&#x…

eventfd(一)

函数原型&#xff1a; 创建的时候可以传入一个计数器的初始值initval。 第二个参数flags在linux 2.6.26之前的版本是没有使用的&#xff0c;必须初始化为0&#xff0c;在2.6.27之后的版本flag才被使用。 #include <sys/eventfd.h> int eventfd(unsigned int initval, in…

gettimeofday

作用&#xff1a;需要打印代码执行到某处的时间&#xff0c;或者需要计算程序执行的时间差&#xff08;精确到微妙级&#xff09;。这时会用到gettimeofday函数&#xff0c;它可以返回自1970-01-01 00:00:00到现在经历的秒数。 #include <sys/time.h> int gettimeofday(…

1070 结绳 (25 分

给定一段一段的绳子&#xff0c;你需要把它们串成一条绳。每次串连的时候&#xff0c;是把两段绳子对折&#xff0c;再如下图所示套接在一起。这样得到的绳子又被当成是另一段绳子&#xff0c;可以再次对折去跟另一段绳子串连。每次串连后&#xff0c;原来两段绳子的长度就会减…

1072 开学寄语 (20 分)

下图是上海某校的新学期开学寄语&#xff1a;天将降大任于斯人也&#xff0c;必先删其微博&#xff0c;卸其 QQ&#xff0c;封其电脑&#xff0c;夺其手机&#xff0c;收其 ipad&#xff0c;断其 wifi&#xff0c;使其百无聊赖&#xff0c;然后&#xff0c;净面、理发、整衣&am…

1076 Wifi密码 (15 分)

下面是微博上流传的一张照片&#xff1a;“各位亲爱的同学们&#xff0c;鉴于大家有时需要使用 wifi&#xff0c;又怕耽误亲们的学习&#xff0c;现将 wifi 密码设置为下列数学题答案&#xff1a;A-1&#xff1b;B-2&#xff1b;C-3&#xff1b;D-4&#xff1b;请同学们自己作答…

c++如何防止一个类被其他类继承?

如何在防止一个类被其他的类继承呢&#xff1f; 如果是仅仅为了达到这个目的可以直接把这个类的构造函数设置成私有的&#xff0c;这样就杜绝了其他类的继承。也相当于毁掉了这个类&#xff08;无法再创造出自己的对象&#xff09;。 那么怎么样既要保证这个类的完整性&#…

C++中构造函数和析构函数可以抛出异常吗?

不建议在构造函数中抛出异常。当构造函数中抛出异常时&#xff0c;析构函数将不会被执行&#xff0c;需要手动释放内存。析构函数不应该抛出异常。当析构函数中有一些可能发生的异常时&#xff0c;这时候要把可能发生的异常完全封装在析构函数内部&#xff0c;决不能让它抛出到…

1086 就不告诉你 (15 分)

做作业的时候&#xff0c;邻座的小盆友问你&#xff1a;“五乘以七等于多少&#xff1f;”你应该不失礼貌地围笑着告诉他&#xff1a;“五十三。”本题就要求你&#xff0c;对任何一对给定的正整数&#xff0c;倒着输出它们的乘积。 输入格式&#xff1a; 输入在第一行给出两个…

多线程顺序交替打印ABCD

题目&#xff1a;按照 ABCD的顺序交替打印。 1. 测试代码&#xff1a; #include <iostream> #include <unistd.h> #include <stdlib.h> #include <pthread.h> using namespace std;struct {int t;pthread_mutex_t mutex;pthread_cond_t cond; } tes…

1092 最好吃的月饼 (20 分

月饼是久负盛名的中国传统糕点之一&#xff0c;自唐朝以来&#xff0c;已经发展出几百品种。 若想评比出一种“最好吃”的月饼&#xff0c;那势必在吃货界引发一场腥风血雨…… 在这里我们用数字说话&#xff0c;给出全国各地各种月饼的销量&#xff0c;要求你从中找出销量冠军…

1094 谷歌的招聘 (20 分)

2004 年 7 月&#xff0c;谷歌在硅谷的 101 号公路边竖立了一块巨大的广告牌&#xff08;如下图&#xff09;用于招聘。内容超级简单&#xff0c;就是一个以 .com 结尾的网址&#xff0c;而前面的网址是一个 10 位素数&#xff0c;这个素数是自然常数 e 中最早出现的 10 位连续…

C++基础:各种输入方法总结

输入原理简述&#xff1a; 程序的输入都建有一个缓冲区&#xff0c;即输入缓冲区。每次输入过程是这样的&#xff0c;当一次键盘输入结束时会将输入的数据存入输入缓冲区&#xff0c;而cin函数直接从输入缓冲区中取数据。正因为cin函数是直接从缓冲区取数据的&#xff0c;所以…

03-树1 树的同构 (25 分)

给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2&#xff0c;则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的&#xff0c;因为我们把其中一棵树的结点A、B、G的左右孩子互换后&#xff0c;就得到另外一棵树。而图2就不是同构的。 图1 图2 现给定两棵…

06-图3 六度空间 (30 分)

“六度空间”理论又称作“六度分隔&#xff08;Six Degrees of Separation&#xff09;”理论。这个理论可以通俗地阐述为&#xff1a;“你和任何一个陌生人之间所间隔的人不会超过六个&#xff0c;也就是说&#xff0c;最多通过五个人你就能够认识任何一个陌生人。”如图1所示…

命令行工具tshark使用小记

1、目的 写这篇博客的目的主要是为了方便查阅&#xff0c;使用wireshark可以分析数据包&#xff0c;可以通过编辑过滤表达式来达到对数据的分析&#xff1b;但我的需求是&#xff0c;怎么样把Data部分导出来&#xff0c;因为后续的工作主要针对数据包的Data部分&#xff0c;主要…

wireshark源代码分析

各位亲&#xff0c;不是我不想回复你们的问题。是我也不了解。不能误导。希望大家相互帮助。看看能否帮那些提问的小盆友们回复一下呢&#xff1f; 这些都是转载的&#xff0c;如果实在没有办法&#xff0c;可以打开链接到原作者哪里去提问试试看。。。 经过多次尝试&#xf…

Defunct进程 僵尸进程

在测试基于 DirectFBGstreamer 的视频联播系统的一个 Demo 的时候&#xff0c;其中大量使用 system 调用的语句&#xff0c;例如在 menu 代码中的 system("./play") &#xff0c;而且多次执行&#xff0c;这种情况下&#xff0c;在 ps -ef 列表中出现了大量的 defunc…

make文件基础用法

参照&#xff1a;https://www.jianshu.com/p/0b2a7cb9a469 创建工作目录&#xff0c;包含一下文件 main.cperson.cb.hc.h/*** c.h ***/ //this is c.h /*** b.h ***/ //this is b.h /*** main.c ***/ #include<stdio.h> //#include"a1.h" //#include"b.h&…