父类的父类指针指向子类对象的子类的对象,调用虚函数,会输出什么

C++ 有没有可能通过父类指针调用子类的虚函数? - 知乎3被浏览892分享邀请回答2添加评论分享收藏感谢收起写回答c++中多态性、dynamic_cast、父类指针、父类对象、子类指针、子类对象 - 矮油~ - 博客园
&&& c++多态性是依靠虚函数和父类指针指向子类对象来实现的。简单来说,父类中定义虚函数,父类指针指向子类对象,父类指针调用函数时调用的就是子类的函数。
&&& 父类没有定义虚函数,父类指针指向子类对象时,父类指针调用的函数还是父类的函数。
&&& dynamic_cast可以实现将子类指针动态转换成父类指针(相当于父类指针指向了子类对象),用dynamic_cast时,父类必须要有virtual声明的虚函数。
&& 父类指针转换成子类指针(也就是子类指针指向了父类对象)是危险的,dynamic_cast失败会返回NULL,static_cast可以转换,但是有越界危险。
& &dynamic_cast如果转化的是引用,失败会抛出异常。
下面是代码:
class BaseClass {
virtualvoid foo(){}; //基类必须有虚函数。保持多态特性才能使用dynamic_cast
class DerivedClass: public BaseClass {
char*m_szName[100];
void bar(){};
BaseClass* pb =new DerivedClass();
DerivedClass *pd1 = static_cast&DerivedClass *&(pb); //子类-&父类,静态类型转换,正确但不推荐
DerivedClass *pd2 = dynamic_cast&DerivedClass *&(pb); //子类-&父类,动态类型转换,正确
BaseClass* pb2 =new BaseClass();
DerivedClass *pd21 = static_cast&DerivedClass *&(pb2); //父类-&子类,静态类型转换,危险!访问子类m_szName成员越界
DerivedClass *pd22 = dynamic_cast&DerivedClass *&(pb2); //父类-&子类,动态类型转换,安全的。结果是NULL
下面是一些资料:
1 什么是多态?多态性可以简单的概括为&1个接口,多种方法&,在程序运行的过程中才决定调用的机制程序实现上是这样,通过父类指针调用子类的函数,可以让父类指针有多种形态。2 实现机制举一个例子:#include &iostream.h&class animal{public:void sleep(){cout&&"animal sleep"&&}void breathe(){cout&&"animal breathe"&&}};class fish:public animal{public:void breathe(){cout&&"fish bubble"&&}};void main(){animal *pAn=&pAn-&breathe();}答案是输出:animal breathe结果分析:1从编译的角度C++编译器在编译的时候,要确定每个对象调用的函数的地址,这称为早期绑定(early binding),当我们将fish类的对象fh的地址赋给pAn时,C++编译器进行了类型转换,此时C++编译器认为变量pAn保存的就是animal对象的地址。当在main()函数中执行pAn-&breathe()时,调用的当然就是animal对象的breathe函数。2 内存模型的角度我们构造fish类的对象时,首先要调用animal类的构造函数去构造animal类的对象,然后才调用fish类的构造函数完成自身部分的构造,从而拼接出一个完整的fish对象。当我们将fish类的对象转换为animal类型时,该对象就被认为是原对象整个内存模型的上半部分,也就是图1-1中的&animal的对象所占内存&。那么当我们利用类型转换后的对象指针去调用它的方法时,当然也就是调用它所在的内存中的方法。因此,输出animal breathe,也就顺理成章了。为了得到我们想要的结果,就要使用虚函数前面输出的结果是因为编译器在编译的时候,就已经确定了对象调用的函数的地址,要解决这个问题就要使用迟绑定(late binding)技术。当编译器使用迟绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器采用迟绑定,就要在基类中声明函数时使用virtual关键字(注意,这是必须的,很多学员就是因为没有使用虚函数而写出很多错误的例子),这样的函数我们称为虚函数。一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,而不需要再显式地声明为virtual。&下面我们将上面一段代码进行部分修改virtual void breathe(){cout&&"animal breathe"&&}运行结果:fish bubble结果分析编译器为每个类的对象提供一个虚表指针,这个指针指向对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。& & 由于pAn实际指向的对象类型是fish,因此vptr指向的fish类的vtable,当调用pAn-&breathe()时,根据虚表中的函数地址找到的就是fish类的breathe()函数。正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的。换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数。那么虚表指针在什么时候,或者说在什么地方初始化呢?答案是在构造函数中进行虚表的创建和虚表指针的初始化。还记得构造函数的调用顺序吗,在构造子类对象时,要先调用父类的构造函数,此时编译器只&看到了&父类,并不知道后面是否后还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。& & &当fish类的fh对象构造完毕后,其内部的虚表指针也就被初始化为指向fish类的虚表。在类型转换后,调用pAn-&breathe(),由于pAn实际指向的是fish类的对象,该对象内部的虚表指针指向的是fish类的虚表,因此最终调用的是fish类的breathe()函数。&
为了更加清楚的说明内存分布:下面详细的介绍内存的分布
1 基类的内存分布情况
请看下面的sampleclass A{void g(){.....}};则sizeof(A)=1;如果改为如下:class A{public:&&& virtual void f()&&& {&&&&&& ......&&& }&&& void g(){.....}}则sizeof(A)=4! 这是因为在类A中存在virtual function,为了实现多态,每个含有virtual function的类中都隐式包含着一个静态虚指针vfptr指向该类的静态虚表vtable, vtable中的表项指向类中的每个virtual function的入口地址例如 我们declare 一个A类型的object :&&& A&&& A则编译后其内存分布如下:& &&从 vfptr所指向的vtable可以看出,每个virtual function都占有一个entry,例如本例中的f函数。而g函数因为不是virtual类型,故不在vtable的表项之内。说明:vtab属于类成员静态pointer,而vfptr属于对象pointer2 继承类的内存分布状况假设代码如下:public B:public A{public :&&& int f() //override virtual function&&& {&&&&&&& return 3;&&& }};则AAB编译后,其内存分布如下:从中我们可以看出,B类型的对象e有一个vfptr指向vtable address:0x ,而A类型的对象c和d共同指向类的vtable address:0xa3 动态绑定过程的实现&&& 我们说多态是在程序进行动态绑定得以实现的,而不是编译时就确定对象的调用方法的静态绑定。&&& 其过程如下:&&& 程序运行到动态绑定时,通过基类的指针所指向的对象类型,通过vfptr找到其所指向的vtable,然后调用其相应的方法,即可实现多态。例如:ABA *pc=&e; //设置breakpoint,运行到此处pc=&c;此时内存中各指针状况如下:
可以看出,此时pc指向类B的虚表地址,从而调用对象e的方法。
继续运行,当运行至pc=&c时候,此时pc的vptr值为0x,即指向类A的vtable地址,从而调用c的方法。这就是动态绑定!(dynamic binding)或者叫做迟后联编(lazy compile)。
& & 总结:&& & 对于虚函数调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。& &需要注意的几点& &总结(基类有虚函数):& & &1、每一个类都有虚表。& & &2、虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。& & &3、派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。2 一个很好的例子 (this指针是指向子类)#include &iostream.h&base *class base{public:base(){pbase=}virtual void fn(){cout&&"base"&&}};class derived:public base{void fn(){cout&&"derived"&&}};void main(){pbase-&fn();}& & 我在base类的构造函数中将this指针保存到pbase全局变量中。在定义全局对象aa,即调用时,要调用基类的构造函数,先构造基类的部分,然后是子类的部分,由这两部分拼接出完整的对象aa。这个this指针指向的当然也就是aa对象,那么我们在main()函数中利用pbase调用fn(),因为pbase实际指向的是aa对象,而aa对象内部的虚表指针指向的是自身的虚表,最终调用的当然是derived类中的fn()函数。& & 在这个例子中,由于我的疏忽,在derived类中声明fn()函数时,忘了加public关键字,导致声明为了private(默认为private),但通过前面我们所讲述的虚函数调用机制,我们也就明白了这个地方并不影响它输出正确的结果。不知道这算不算C++的一个Bug,因为虚函数的调用是在运行时确定调用哪一个函数,所以编译器在编译时,并不知道pbase指向的是aa对象,所以导致这个奇怪现象的发生。如果你直接用aa对象去调用,由于对象类型是确定的(注意aa是对象变量,不是指针变量),编译器往往会采用早期绑定,在编译时确定调用的函数,于是就会发现fn()是私有的,不能直接调用。:)& &许多学员在写这个例子时,直接在基类的构造函数中调用虚函数,前面已经说了,在调用基类的构造函数时,编译器只&看到了&父类,并不知道后面是否后还有继承者,它只是初始化父类对象的虚表指针,让该虚表指针指向父类的虚表,所以你看到结果当然不正确。只有在子类的构造函数调用完毕后,整个虚表才构建完毕,此时才能真正应用C++的多态性。换句话说,我们不要在构造函数中去调用虚函数,当然如果你只是想调用本类的函数,也无所谓。
&& 谈到虚函数,不防将虚函数和纯虚函数做个比较
&引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数。
& 纯虚函数&引入原因:为了实现多态性,纯虚函数有点像java中的接口,自己不去实现过程,让继承他的子类去实现。
&&& 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。 这时我们就将动物类定义成抽象类,也就是包含纯虚函数的类&&& 纯虚函数就是基类只定义了函数体,没有实现过程定义方法如下
& virtual void Eat() = 0; 直接=0 不要 在cpp中定义就可以了&虚函数和纯虚函数的区别1虚函数中的函数是实现的哪怕是空实现,它的作用是这个函数在子类里面可以被重载,运行时动态绑定实现动态纯虚函数是个接口,是个函数声明,在基类中不实现,要等到子类中去实现2 虚函数在子类里可以不重载,但是虚函数必须在子类里去实现。&>&c++中子类对象不能调用父类中的虚函数
c++中子类对象不能调用父类中的虚函数
上传大小:329B
c++里,指针和引用是很重要的概念,这个程序不仅对指针和引用做了说明、使用,而且对子类不能继承父类虚函数也做了说明。
综合评分:3
下载个数:
{%username%}回复{%com_username%}{%time%}\
/*点击出现回复框*/
$(".respond_btn").on("click", function (e) {
$(this).parents(".rightLi").children(".respond_box").show();
e.stopPropagation();
$(".cancel_res").on("click", function (e) {
$(this).parents(".res_b").siblings(".res_area").val("");
$(this).parents(".respond_box").hide();
e.stopPropagation();
/*删除评论*/
$(".del_comment_c").on("click", function (e) {
var id = $(e.target).attr("id");
$.getJSON('/index.php/comment/do_invalid/' + id,
function (data) {
if (data.succ == 1) {
$(e.target).parents(".conLi").remove();
alert(data.msg);
$(".res_btn").click(function (e) {
var parentWrap = $(this).parents(".respond_box"),
q = parentWrap.find(".form1").serializeArray(),
resStr = $.trim(parentWrap.find(".res_area_r").val());
console.log(q);
//var res_area_r = $.trim($(".res_area_r").val());
if (resStr == '') {
$(".res_text").css({color: "red"});
$.post("/index.php/comment/do_comment_reply/", q,
function (data) {
if (data.succ == 1) {
var $target,
evt = e || window.
$target = $(evt.target || evt.srcElement);
var $dd = $target.parents('dd');
var $wrapReply = $dd.find('.respond_box');
console.log($wrapReply);
//var mess = $(".res_area_r").val();
var mess = resS
var str = str.replace(/{%header%}/g, data.header)
.replace(/{%href%}/g, 'http://' + window.location.host + '/user/' + data.username)
.replace(/{%username%}/g, data.username)
.replace(/{%com_username%}/g, data.com_username)
.replace(/{%time%}/g, data.time)
.replace(/{%id%}/g, data.id)
.replace(/{%mess%}/g, mess);
$dd.after(str);
$(".respond_box").hide();
$(".res_area_r").val("");
$(".res_area").val("");
$wrapReply.hide();
alert(data.msg);
}, "json");
/*删除回复*/
$(".rightLi").on("click", '.del_comment_r', function (e) {
var id = $(e.target).attr("id");
$.getJSON('/index.php/comment/do_comment_del/' + id,
function (data) {
if (data.succ == 1) {
$(e.target).parent().parent().parent().parent().parent().remove();
$(e.target).parents('.res_list').remove()
alert(data.msg);
//填充回复
function KeyP(v) {
var parentWrap = $(v).parents(".respond_box");
parentWrap.find(".res_area_r").val($.trim(parentWrap.find(".res_area").val()));
评论共有6条
简单的例子,初学者可以看看,不过又没注释
还好,自己学习一下
果然如楼下所言,只有代码,去了论坛才问明白的
VIP会员动态
CSDN下载频道资源及相关规则调整公告V11.10
下载频道用户反馈专区
下载频道积分规则调整V1710.18
spring mvc+mybatis+mysql+maven+bootstrap 整合实现增删查改简单实例.zip
资源所需积分/C币
当前拥有积分
当前拥有C币
输入下载码
为了良好体验,不建议使用迅雷下载
c++中子类对象不能调用父类中的虚函数
会员到期时间:
剩余下载个数:
剩余积分:0
为了良好体验,不建议使用迅雷下载
积分不足!
资源所需积分/C币
当前拥有积分
您可以选择
程序员的必选
绿色安全资源
资源所需积分/C币
当前拥有积分
当前拥有C币
为了良好体验,不建议使用迅雷下载
资源所需积分/C币
当前拥有积分
当前拥有C币
为了良好体验,不建议使用迅雷下载
资源所需积分/C币
当前拥有积分
当前拥有C币
您的积分不足,将扣除 10 C币
为了良好体验,不建议使用迅雷下载
无法举报自己的资源
你当前的下载分为234。
你还不是VIP会员
开通VIP会员权限,免积分下载
你下载资源过于频繁,请输入验证码
您因违反CSDN下载频道规则而被锁定帐户,如有疑问,请联络:!
若举报审核通过,可返还被扣除的积分
被举报人:
举报的资源分:
请选择类型
资源无法下载 ( 404页面、下载失败、资源本身问题)
资源无法使用 (文件损坏、内容缺失、题文不符)
侵犯版权资源 (侵犯公司或个人版权)
虚假资源 (恶意欺诈、刷分资源)
含色情、危害国家安全内容
含广告、木马病毒资源
*详细原因:
c++中子类对象不能调用父类中的虚函数父类指针可以指向子类对象,反之则不能 - 矮油~ - 博客园
简单来说,C++的多态就是靠父类指针指向子类对象+虚函数来实现的。父类指针指向子类对象,可以调用子类从父类继承来的那一部分,但如果父类中声明了virtual,则可以调用子类中的方法,这样就实现了多态。而子类指针指向父类对象,可能会调用到父类中没用的方法,因此这是不对的。至于两类指针的互换是另一个问题。例如:class a{public:int aa};class b:public a{public:}从内存的来看如a---------||占一个int数据大小--||----(aa数据)------||---------而b则是---------|---------|占一个int数据大小--|占一个Int数据大小--||从a中继承而来------|---(bb数据----------||------------------当定义一个基类类型的指针时a *p;这时,这个指针指向的是a类型的数据当p指针指向派生类的时候,因为p是a类型的指针,所以*p只解释为a类型数据的长度,即&&&&&&&&-|---------|占一个int数据大小--|占一个Int数据大小--||从a中继承而来------|-----(bb数据)-------||------------------|------------|------------| |-p只指向这个区域_--|因此,当基类的指针(P)指向派生类的时候,只能操作派生类中从基类中继承过来的数据。指向派生类的指针,因为内存空间比基类长,会导致严重了后果,所以不允许派生类的指针指向基类。而基类的指针可以指向派生类。C++的多态性能解决基类指针不能操作派生类的数据成员的问题。已知指向子类对象的基类指针,怎样用基类指针将子类的数据成员全部输出?
[问题点数:40分,结帖人kejian_clear]
已知指向子类对象的基类指针,怎样用基类指针将子类的数据成员全部输出?
[问题点数:40分,结帖人kejian_clear]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2015年8月 C/C++大版内专家分月排行榜第三2015年5月 C/C++大版内专家分月排行榜第三2015年3月 C/C++大版内专家分月排行榜第三2015年1月 C/C++大版内专家分月排行榜第三
2016年2月 C/C++大版内专家分月排行榜第三2016年1月 C/C++大版内专家分月排行榜第三
2016年2月 C/C++大版内专家分月排行榜第三2016年1月 C/C++大版内专家分月排行榜第三
2016年2月 C/C++大版内专家分月排行榜第三2016年1月 C/C++大版内专家分月排行榜第三
2016年2月 C/C++大版内专家分月排行榜第三2016年1月 C/C++大版内专家分月排行榜第三
匿名用户不能发表回复!|}

我要回帖

更多关于 父类指针指向子类对象 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信