C++ 虚函数在基类与派生类对象间的表现及其分析
近来看了侯捷的《深入浅出MFC》,读到C++重要性质中的虚函数与多态那部分内容时,顿时有了疑惑。因为书中说了这么一句:使用“基类之指针”指向“派生类之对象”,由该指针只能调用基类所定义的函数,如果要让基类的指针使用派生类中定义的函数,就将该函数定义为虚函数。
但在“Object slicing与虚函数”这一小节给出了一个及其经典的例子,它指出,在向上(即向基类)强制转型时,会造成对象内容的被切割。
下面用示例进行说明:
#include "stdafx.h"
#include <iostream>
using namespace std; class A
{
public:
virtual void fn(){cout<<"A fn"<<endl;}
}; class B: public A
{
public:
virtual void fn(){cout<<"B fn"<<endl;}
}; int main(int argc, char* argv[])
{
B b1;
A a1=(A)b1;
A * a2=(A*)&b1;
a1.fn();
a2->fn();
return ;
}
结果如下:
通过调试分析其内存模型如下:
可知,通过A a1=(A)b1传值时,对象中指向虚函数表的指针__vfptr值不同,但是通过A *a2=(A*)&b1传址操作时,对象中指向虚函数表的指针__vfptr值是相同的,调用的是类B对象的虚函数表中虚函数fn()。注意,对象调用的是哪个类的虚函数就要看类对象的虚函数表中的函数是哪一个。这句话也好理解,a1虚函数表中fn()是A::fn(),a2虚函数表中fn()是B::fn(),所以出现以上结果。
在如下包含父类的父类的继承中:
class A
{
public:
virtual void fn(){cout<<"A fn"<<endl;}
}; class B: public A
{
public:
virtual void fn(){cout<<"B fn"<<endl;}
}; class C: public B
{
public:
};
C c;
A a1=(A)c;
A * a2=(A*)&c;
a1.fn();
a2->fn();
这时,a1虚函数表中fn()为A::fn(),a2虚函数表中fn()为B::fn(),因为类C对象的虚函数表中fn()为B::fn(),a2中指向虚函数表的指针值与类C对象指向虚函数表的指针值相同。
侯捷的书中有句话是这么对它进行解释的:由于(A)b1.fn()是传值而非传地址操作,编译器以所谓的拷贝构造函数把A对象内容复制了一份,使得b1的虚函数表内容与A对象的虚函数表内容相同。
看来,得多读圣贤书,读懂读透啊!
C++ 虚函数在基类与派生类对象间的表现及其分析的更多相关文章
- (转) C++中基类和派生类之间的同名函数的重载问题
下面有关派生类与基类中存在同名函数 fn: class A { public: void fn() {} void fn(int a) {} }; class B : public A { publi ...
- 构造函数为什么不能为虚函数 & 基类的析构函数为什么要为虚函数
一.构造函数为什么不能为虚函数 1. 从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的.问题出来了,假设构造函数 ...
- C++中虚基类在派生类中的内存布局
今天重温C++的知识,当看到虚基类这点的时候,那时候也没有太过追究,就是知道虚基类是消除了类继承之间的二义性问题而已,可是很是好奇,它是怎么消除的,内存布局是怎么分配的呢?于是就深入研究了一下,具体的 ...
- OOP2(虚函数/抽象基类/访问控制与继承)
通常情况下,如果我们不适用某个函数,则无需为该函数提供定义.但我们必须为每个虚函数都提供定义而不管它是否被用到了,这因为连编译器也无法确定到底会适用哪个虚函数 对虚函数的调用可能在运行时才被解析: 当 ...
- 详解C++中基类与派生类的转换以及虚基类
很详细!转载链接 C++基类与派生类的转换在公用继承.私有继承和保护继承中,只有公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外的基类所有成员,基类的公用或保护成员的访问权限在派生类中 ...
- 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)
[源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...
- C++基类和派生类之间的转换
本文讲解内容的前提是派生类继承基类的方式是公有继承,关键字public 以下程序为讲解用例. #include<iostream> using namespace std; class A ...
- OOP1(定义基类和派生类)
面向对象程序设计基于三个基本概念:数据抽象,继承和动态绑定 数据抽象是一种依赖于接口和实现分离的编程技术.继承和动态绑定对程序的编号有两方面的影响:一是我们可以更容易地定义与其它类相似但不完全相同的类 ...
- c++中基类与派生类中隐含的this指针的分析
先不要看结果,看一下你是否真正了解了this指针? #include<iostream> using namespace std; class Parent{ public: int x; ...
随机推荐
- 如何利用java得到当前的时间和前一天的时间
1.得到当前的时间 Calendar cal = Calendar.getInstance(); String time=formatter.format(cal.getTime()); ...
- 关于IE8及其以下的IE版本不支持getElementsByClassName
之前做一下项目的时候知道IE8以及其以下的版本不支持getElementsByClassName,于是乎自己写了一个函数重新定义getElementsByClassName,函数代码如下: funct ...
- 气象API(2)
中华万年历: http://wthrcdn.etouch.cn/weather_mini?city=北京通过城市名字获得天气数据,json数据http://wthrcdn.etouch.cn/weat ...
- iOS AFNetWorking 下载pdf文档
+ (void)downLoadPdf:(NSString *)url pdf_id:(NSString *)pdf_id block:(APIFilePath)pdfFilePath { NS ...
- berkeley db replica机制 - 主从同步
repmgr/repmgr_net.c, __repmgr_send(): 做send_broadcast, 然后根据policy 对DB_REP_PERMANENT的处理 __repmgr_send ...
- Debian 8 最小化系统安装muduo
最近开始学习陈硕的muduo C++ Linux多线程网络库,首先当然是要安装.其间遇到过不少问题,最后不能说都解决了,只能说找到了一条不会遇到什么问题的路线.总结起来是两点: 注意各软件之间要版本匹 ...
- 今天在Mac机器上使用了Flex Builder编辑了一个源代码文件,保存后使用vim命令去打开时发现系统自动在每一行的结尾添加了^M符号,其实^M在Linux/Unix中是非常常见的,也就是我们在Win中见过的/r回车符号。由于编辑软件的编码问题,某些IDE的编辑器在编辑完文件之后会自动加上这个^M符号。看起来对我们的源代码没有任何影响,其实并不然,当我们把源代码文件Check In到svn之类
今天在Mac机器上使用了Flex Builder编辑了一个源代码文件,保存后使用vim命令去打开时发现系统自动在每一行的结尾添加了^M符号,其实^M在Linux/Unix中是非常常见的,也就是我们在W ...
- codeforces 429E
题意:给定n<=100000线段[l,r],然后给这些线段染色(red or blue),求最后平面上任意一个点被蓝色及红色覆盖次数只差的绝对值不大于1 思路:把每条线段拆成2个点[l<& ...
- C++混合编程之idlcpp教程Python篇(2)
在上一篇 C++混合编程之idlcpp教程(一) 中介绍了 idlcpp 工具的使用.现在对 idlcpp 所带的示例教程进行讲解,这里针对的 Python 语言的例子.首先看第一个示例程序 Pyth ...
- 精确运算--BigDecimal
工具类 ,直接就可以使用,对于float和double的进行加减乘除长生的误差的解决办法. import java.math.BigDecimal; public class Arith { // 默 ...