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; ...
随机推荐
- jsp的9大对象
1.requset对象 主要用于接受客户端通过HTTP协议传送给服务器端的数据 request.getProtocal()获得客户使用协议 request.getServletPath ...
- Jade之Doctype
Doctype jade: doctype html html: <!DOCTYPE html>
- 如何查看 oracle 官方文档
Concept 包含了 oracle 数据库里面的一些基本概念和原理, 比如 数据库逻辑结构, 物理结构, 实例结构, 优化器, 事务等. PDF 460页 Reference 包含了动态性能视图, ...
- iOS Multipart上传单张图片
#pragma mark - uploadFile - (void)uploadFile:(NSDictionary *)dic { NSURL *URL = [[NSURL alloc] ...
- Asp.net中HttpRequest.Params与Reques.Item之异同
今天才注意到HttpRequest.Params与HttpRequest.Item这两个玩意竟然有微妙的不同.上午的时候同事被坑了发现这玩意的说明还真微妙. 场景再现: 前台提交一个POST请求到后台 ...
- 我的Linux随笔目录
现在整理博客的时间少了,大多是在用为知笔记收藏和整理,一次集中发点Linux相关随笔整理和一个目录,是按时间顺序来的.每一篇都是自己用过之后整理的,应用场景已经尽可能的说明了,不明白的可以Q我,上班时 ...
- 安装GO语言环境之安装Visual Studio Code插件
在安装Visual Studio Code插件的时候,由于谷歌的限制,在下载下列插件的时候会报错: go get -u -v github.com/nsf/gocode go get -u -v gi ...
- 跟我一起学WCF(10)——WCF中事务处理
一.引言 好久没更新,总感觉自己欠了什么一样的,所以今天迫不及待地来更新了,因为后面还有好几个系列准备些,还有很多东西需要学习总结的.今天就来介绍下WCF对事务的支持. 二.WCF事务详解 2.1 事 ...
- Asp.Net Web API 2第一课——入门
详情请查看http://aehyok.com/Blog/Detail/68.html 个人网站地址:aehyok.com QQ 技术群号:206058845,验证码为:aehyok 本文文章链接:ht ...
- .NET Core是什么?
[初步理解] .NET Core 是一个开源的.跨平台的 .NET 实现.而 .NET Framework 是基于 Windows 的 .NET 实现,Mono 是 .NET Framework 的一 ...