C++之易混淆知识点四---虚函数与虚继承
C++面向对象中,虚函数与虚继承是两个完全不同的概念。
一、虚函数
C++程序中只要类中含有虚拟函数,编译程序都会为此类生成一个对应的虚拟函数跳转表(vtbl),该虚拟函数跳转表是一个又若干个虚拟函数体入口地址组成的一个线性表。派生类的虚拟函数跳转表的前半部分由父类的vtbl得出,但是里面的内容不一定相同,后半部分则对应着自己新定义的虚拟函数。
class Employee
{
protected:
char *Name;
int Age;
public:
void changeAge(int newAge);
virtual void retire(); //虚函数
Employee( char *n, int a );
~Employee();
}; class Manager: public Employee
{
int Level;
public:
void changeLevel( int l);
void retire(); //情况一、子类覆写了虚函数
Manager( char *n, int a, int l );
~Manager();
};
情况一、子类覆写了父类的虚函数,则此时子类和父类的虚函数表分别为:

情况二、子类没有覆写父类的虚函数,则此时子类和父类的虚函数表分别为:
class Manager: public Employee
{
int Level;
public:
void changeLevel( int l);
//void retire(); //情况二、子类没有覆写虚函数
Manager( char *n, int a, int l );
~Manager();
};

情况三、子类中自己的虚函数,则此时子类和父类的虚函数表分别为:
class Manager: public Employee
{
int Level;
public:virtual void changeLevel(int l); //情况三、子类有自己的虚拟函数
void retire();
Manager( char *n, int a, int l );
~Manager();
};

二、虚继承
虚继承是为了解决多重继承中的问题而出现的。要理解虚继承的实现机制,首先看一般继承:
class A
{
char k[];
public:
virtual void aa()
{ };
};
class B:public A
{
char j[];
public:
virtual void bb()
{ };
};
class C:public B
{
char i[];
public:
virtual void cc()
{ };
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
return ;
}
得到的答案是:8 12 16
分析如下:
1、 对于类A,有一个虚函数,需要一个虚函数表vtbl来记录函数的入口地址,每个地址一个虚指针,指针的大小为4,类中还有一个成员变量char k[3],则根据数据对齐原则,可以得到sizeof(A)的大小为8;
2、 对于类B,也有一个虚函数,需要一个虚函数表vtbl来记录函数的入口地址,每个地址一个虚指针,指针的大小为4,类中还有一个成员变量j[3],但是类B是继承自类A的,是一般继承,不是虚继承,所以可以得到sizeof(B)的大小为: 4(指向虚函数的虚指针)+4(自己的数据成员)+4(父类A的数据成员)=12;
3、 对于类C,也有一个虚函数,需要一个虚函数表vtbl来记录函数的入口地址,每个地址一个虚指针,指针的大小为4,类中还有一个成员变量i[3],同样的,类C是继承自类B的,是一般继承,不是虚继承,所以可以得到sizeof(C)的大小为4(指向虚函数的虚指针)+4(自己的数据成员)+8(父类的数据成员)=16;

再来看一下虚继承的情况:
class A
{
char k[];
public:
virtual void aa()
{ };
};
class B:public virtual A
{
char j[];
public:
virtual void bb()
{ };
};
class C:public virtual B
{
char i[];
public:
virtual void cc()
{ };
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
return ;
}
得到的答案是:8 20 32
分析如下:
1、对于类A,有一个虚函数,需要一个虚函数表vtbl来记录函数的入口地址,每个地址一个虚指针,指针的大小为4,类中还有一个成员变量k[3],则根据数据对齐原则,可以得到sizeof(A)的大小为8;
2、对于类B,也有一个虚函数,需要一个虚函数表vtbl来记录函数的入口地址,每个地址一个虚指针,指针的大小为4,类中还有一个成员变量j[3],但是类B是继承自类A的,而且是虚继承,虚继承的实现是通过一个虚基类指针列表,比如vptr_B_A指向虚基类,同时还包含了父类的所有内容,所以可以得到sizeof(B)的大小为:4(指向自己虚函数的虚指针)+4(自己的数据成员)+4(指向虚基类的指针vptr_B_A)+8(父类A的内容大小)=20;
3、对于类C,也有一个虚函数,需要一个虚函数表vtbl来记录函数的入口地址,每个地址一个虚指针,指针的大小为4,类中还有一个成员变量i[3],同样的,类C是继承自类B的,而且是虚继承,虚继承的实现是通过一个虚基类指针列表,比如vptr_C_B指向虚基类,同时还包含了父类的所有内容,所以可以得到sizeof(C)的大小为:4(指向自己虚函数的虚指针)+4(自己的数据成员)+4(指向虚基类的指针(vptr_C_B)+20(父类B的内容大小)=32;
class A
{
char k[];
public:
virtual void aa()
{ };
};
class B:public virtual A
{
char j[];
public:
virtual void bb()
{ };
};
class C:public virtual A
{
char i[];
public:
virtual void cc()
{ };
};
class D: public B,public C
{
char n[];
public:
virtual void dd()
{ };
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
cout<<sizeof(D)<<endl;
return ;
}
得到的结果为:8 20 20 36
分析如下:
1、类sizeof(A,B,C),从前面的分析可以得到结果为8 20 20;
2、sizeof(D)=4(指向自己虚函数的虚指针)+4(自己的数据成员)+
12(父类B的内容,包括数据成员、指向虚函数的虚指针、以及虚基类指针列表vptr_B_A)+
12(父类C的内容,包括数据成员、指向虚函数的虚指针、以及虚基类指针列表vptr_C_A)+
4(类A的数据成员)=36。
可见,类D中只包含了类A的一份副本,虚继承很好地解决了多重继承中的二义性问题。
C++之易混淆知识点四---虚函数与虚继承的更多相关文章
- Java-web易混淆知识点整理
Java-web易混淆知识点 post和get区别 post: 数据不会显示在地址栏 安全 大小无限制 可以提交二进制文件 get: 数据显示在地址栏 不安全 get方式提交有大小限制(约4kb) 相 ...
- C++虚函数及虚函数表解析
一.背景知识(一些基本概念) 虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数.纯虚函数(Pure Virtual Functio ...
- C++基础知识 基类指针、虚函数、多态性、纯虚函数、虚析构
一.基类指针.派生类指针 父类指针可以new一个子类对象 二.虚函数 有没有一个解决方法,使我们只定义一个对象指针,就可以调用父类,以及各个子类的同名函数? 有解决方案,这个对象指针必须是一个父类类型 ...
- virtual之虚函数,虚继承
当类中包含虚函数时,则该类每个对象中在内存分配中除去数据外还包含了一个虚函数表指针(vfptr),指向虚函数表(vftable),虚函数表中存放了该类包含的虚函数的地址. 当子类通过虚继承的方式从父类 ...
- C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义
首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不 ...
- C++之虚函数与虚继承详解
准备工作 1.VS2012使用命令行选项查看对象的内存布局 微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout.使用方法 ...
- C++ 由虚基类 虚继承 虚函数 到 虚函数表
//虚基类:一个类可以在一个类族中既被用作虚基类,也被用作非虚基类. class Base1{ public: Base1(){cout<<"Construct Base1!&q ...
- 为何JAVA虚函数(虚方法)会造成父类可以"访问"子类的假象?
首先,来看一个简单的JAVA类,Base. 1 public class Base { 2 String str = "Base string"; 3 protected vo ...
- C++虚函数和虚函数表
前导 在上面的博文中描述了基类中存在虚函数时,基类和派生类中虚函数表的结构. 在派生类也定义了虚函数时,函数表又是怎样的结构呢? 先看下面的示例代码: #include <iostream> ...
随机推荐
- 杭电 1040 As Easy As A+B 【排序】
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1040 解题思路:数据不大,可以直接用冒泡排序 #include<stdio.h> int ...
- JS面向对象(2)——原型链
原型链用于ECMAScript的继承.其思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.说人话,我们知道,一个构造函数Subtype,其原型对象有一个指向构造函数的指针,这是联系构造函数和 ...
- RabbitMQ学习之spring-amqp的重要类的认识
对于大多数应用来说都做了与spring整合,对于rabbitmq来说.也有与spring的整合.可能通过spring的官网找到spring-amqp项目下载.spring-amqp项目包括三个子项目: ...
- matlab学习-使用自带的函数
>> %定义矩阵求最大值>> a=[1 7 3;6 2 9];>> A=max(a);>> a a = 1 7 3 6 2 9 >> A A ...
- java 文件夹不存在的解决方案
使用new File(path).mkdirs()创建所需路径,几十有多层不存在的路径也可以直接创建,切记方法名以s结尾,不带s的智能创建一层不存在的目录,不能自动创建多层目录结构.
- nginx日志按天自动切割
1.编写shell脚本,创建nginx_log.sh文件#!/bin/bashsource /etc/profile#设置Nginx日志文件存放目录log_path="/usr/local/ ...
- Tab 切换效果
今天写的两个小效果都是为了测试我写的单参函数,结果还是有点成功的~~此处是不是想发表情包! Tab效果很简单,这里我就不赘述了,直接上代码: html代码: <div class="c ...
- [luogu3237 HNOI2014] 米特运输 (树形dp)
传送门 Description 米特是D星球上一种非常神秘的物质,蕴含着巨大的能量.在以米特为主要能源的D星上,这种米特能源的运输和储存一直是一个大问题. D星上有N个城市,我们将其顺序编号为1到N, ...
- 深入了解Spring中的容器
1.创建Bean的3种方式 1.1使用构造器创建bean实例 这是最常见的方式,如果不采用构造注入,bean类需要有默认构造函数.如果采用构造注入,则需要配置xml文件的<constructor ...
- some untracked working tree files问题解决
我使用的是idea,情境是在使用git同步代码的时候,出现的错误. 我这里报错是在右上角的显示信息,其中有一个show view的可点击连接 我点击之后将上面展示的文件删除之后重新同步代码,成功.