C++学习之继承篇
今天通过对实验二继承,重载,覆盖的学习,让我更深一步理解了这些概念的区别。
首先来明确一个概念,函数名即地址,也就是说函数名就是个指针。
编译阶段,编译器为每个函数的代码分配一个地址空间并编译函数代码到这个空间中,函数名就指向这个地址空间。
也即每个函数名都有自己唯一的代码空间。
同理,类的成员函数也是如此。
但是,有一点大家一定要记住,C++编译器编译CPP文件时,会根据"C++编译器的函数名修饰规则" 对函数名进行修饰。
(修饰规则大家自己去搜吧,我就不叙述了),前面讲到函数名称的作用是指向函数真实代码的指针。
知道了以上规则,那么我们对函数覆盖便不难理解了。
首先来看看百度百科中函数覆盖的中文描述是:
函数覆盖发生在父类与子类之间,其函数名、参数类型、返回值类型必须同父类中的相对应被覆盖的函数严格一致,
覆盖函数和被覆盖函数只有函数体不同,当派生类对象调用子类中该同名函数时会自动调用子类中的覆盖版本,
而不是父类中的被覆盖函数版本,这种机制就叫做函数覆盖。
我们来写一段函数覆盖的代码
class father
{
public:
void fun()
{cout<<"father's fun"<<endl;}
};
class son:public father
{
public:
void fun()
{cout<<"son's fun"<<endl;}
};
void main()
{
father Father,*pFather;
son Son,*pSon;
int i = sizeof(Father); //此行代码过后,i=1,此行代码无意义只是让大家知道普通成员函数不占用类的实例空间。
// Father.fun(); //此行注释与下行注释是正常的调用函数覆盖相信大家都能理解,所以不在解释
// Sun.fun();
pSon = (son*)&Father; //子类指针指向父类实例是危险的,此例中并没涉及到任何越界,并且为了展示区别
//所以才这样使用,但大家要明白,这样做是危险的。
pFather = (father*)&Son;
pSon->fun(); //函数调用执行了father's fun
pFather->fun(); //函数调用执行了son's fun
}
此时有些人可能就不能理解为什么会出现这种调用结果了,那么大家是否还记得我上面曾提到的"C++编译器的函数名修饰规则"?
我们来根据"C++编译器的函数名修饰规则"再来想想原因。
根据规则,编译器把父类father中的fun函数名编译为"?fun@father@@QAEZ",子类son中的fun函数名编译为"?fun@son@@QAEZ"。
当pSon->fun();调用时,编译器会把pSon所存地址值的类型转化成当前指针类型,而pSon的当前类型为son(这句话不多余,
因为类型是可以随意转换的),
所以表达式"pSon->fun()"全部展开以后得到的函数名称即"?fun@father@@QAEZ"
同理表达式"pFather->fun()"全部展开以后得到的函数名称即"?fun@son@@QAEZ",
既然得出了函数名,那么也就可以根据函数名称,跳转到真实的函数代码实体位置了。
根据以上分析,我觉得"函数覆盖(英文名不知到叫什么只能用中文了)"这个词汇真的容易把人带入歧途,
我的语文又不好,所以还希望哪位语文好的兄弟,来重新翻译下"函数覆盖"这个词汇。
呃,真费劲啊,函数覆盖算是讲完,不知道大家有每有看懂,如果还看不懂的话,我是真没招了。
下面在来讲个虚函数吧。
有了前面的基础,大家应该对函数有了充分的了解。
那么问大家一个问题,为什么一个类实力化后普通成员函数不影响实例的大小?
呵呵,如果一个新手能回答出来,那我这篇东西就不算白写。
正确答案嘛,是因为不需要它来影响实例大小,因为编译器会根据"C++编译器的函数名修饰规则"与"表达式的地址类型",
自动的把成员函数展开成完整的函数名,也就找到了函数的真实地址,所以普通成员函数是不影响实力大小的。
我再来问大家一个问题,你们认为虚函数需不需要影响类的实例大小?
哈哈,这次的答案是需要。
这次我们来写一段虚函数的代码瞧瞧 因为只有在实例内部添加一个指针,才能够完成例如,
class father
{
public:
virtual void fun()
{cout<<"father's fun"<<endl;}
};
class son:public father
{
public:
void fun()
{cout<<"son's fun"<<endl;}
};
void main()
{
father Father,*pFather;
son Son,*pSon;
int i = sizeof(Father); //此行代码过后,i=4,此行代码无意义只是让大家知道虚函数占用类的实例空间。
Father.fun(); //函数执行结果 "father's fun"
Son.fun(); //函数执行结果 "son's fun" 这句简单点说,就是很多人说的动态绑定,我们下面会具体分析。
pSon = (son*)&Father; //再次强调,子类指针指向父类实例是危险的,一旦越界操作,就会引发异常。
pFather = (father*)&Son;
pSon->fun(); //函数执行结果 "father's fun"
pFather->fun(); //函数执行结果 "son's fun" 这两句也是动态绑定,相信还是有不少人不理解,下面具体分析。
}
恩,大家需要调式一下上面的程序,对Father添加监视,你会发现Father实例中莫名其妙的多了一个vftable类型指针对象vfptr。
是了,虚函数的实现靠的就是这个东西了。
IED帮我们在我们的实例外部实例化了一个vftable对象(我知道这句话很绕,但是我不知道该怎么更好的解释了),
同时为我们的实例Father添加了一个指向vftable对象的指针vfptr。
我们继续把监视中的指针vfptr展开,可以看到一个叫[0]的函数指针(别问我这名为啥长成这样,我也没搞清楚),
哈哈,找到了,这里存储了一个地址,这个地址就是一个函数真实地址(如果用的VS2010编译器,你会直观的看到这个地址
所对应的是father::fun这个函数)。
然后,我再来明确一个虚函数规则,就是当你的实例调用虚函数时,最终调用的就是这个vftable类型的成员[0]所存储地址。
那么好了,我们知道子类是完全继承父类的,所以那个vftable类型指针对象vfptr也同时被继承了下来。
IDE同样为我们实例化一个vftable对象,让vfptr来指向这个vftable对象。
而如果我们的子类重写了虚函数,那么IDE在实例化vftable对象时,就会把[0]这个指针重写为新的子类中那个虚函数地址。
如果我们的子类没有重写这个虚函数,那么IDE就会找到距离这个子类关系最近的一个实现了虚函数的父类,
把这个父类中的虚函数地址,写入到子类的[0]中。
这样子类在调用虚函数的时候,就可以实现动态绑定了。
另外父类指针指向子类实例时,因为有了vfptr指针占位,所以当父类指针调用虚函数时,寻址到的vfptr是子类实例的。
而子类的vfptr指向子类自己的vftable对象,所以父类最终调用的会是子类对象的中[0],所以[0]中存的是哪个函数地址。
父类指针最终调用的就会是哪个函数了。
C++学习之继承篇的更多相关文章
- typescript 的 polyfill 学习1-Class 继承篇
Class 继承 js 是多范式的编程语言,同样也是支持面向对象编程的,类 是面向对象中是很重要的概念. 区别于传统的java,c#基于模板的类,js是基于原型的. 类继承一般是通过原型链的方式来实现 ...
- c++学习笔记之继承篇
title: c++学习笔记之继承篇 date: 2017-03-26 16:36:33 tags: [c++,继承,public,virtual,private,protected] categor ...
- 8-C++远征之继承篇-学习笔记
C++远征之继承篇 开篇介绍 整个C++远征计划: 起航->离港->封装->继承 为什么要用继承? 为什么要有继承? 如何来定义基类 <----> 派生类? 基类到派生类 ...
- 一步步学习javascript基础篇(0):开篇索引
索引: 一步步学习javascript基础篇(1):基本概念 一步步学习javascript基础篇(2):作用域和作用域链 一步步学习javascript基础篇(3):Object.Function等 ...
- Python3学习(2)-中级篇
Python3学习(1)-基础篇 Python3学习(2)-中级篇 Python3学习(3)-高级篇 切片:取数组.元组中的部分元素 L=['Jack','Mick','Leon','Jane','A ...
- ASP.NET MVC学习之过滤器篇(2)
下面我们继续之前的ASP.NET MVC学习之过滤器篇(1)进行学习. 3.动作过滤器 顾名思义,这个过滤器就是在动作方法调用前与调用后响应的.我们可以在调用前更改实际调用的动作,也可以在动作调用完成 ...
- PHP学习笔记 - 进阶篇(10)
PHP学习笔记 - 进阶篇(10) 异常处理 抛出一个异常 从PHP5开始,PHP支持异常处理,异常处理是面向对象一个重要特性,PHP代码中的异常通过throw抛出,异常抛出之后,后面的代码将不会再被 ...
- PHP学习笔记 - 进阶篇(3)
PHP学习笔记 - 进阶篇(3) 类与面向对象 1.类和对象 类是面向对象程序设计的基本概念,通俗的理解类就是对现实中某一个种类的东西的抽象, 比如汽车可以抽象为一个类,汽车拥有名字.轮胎.速度.重量 ...
- Python 学习 第十篇 CMDB用户权限管理
Python 学习 第十篇 CMDB用户权限管理 2016-10-10 16:29:17 标签: python 版权声明:原创作品,谢绝转载!否则将追究法律责任. 不管是什么系统,用户权限都是至关重要 ...
随机推荐
- beego5---gosqlite安装
WindowsWindows下的安装也非常简单,只要到 SQLite3 的下载页面,下载 Windows 下的预编译包 DLL 的压缩包(sqlite-dll-win32-x86-XXX.zip),然 ...
- Buildroot构建指南——根文件系统(Rootfs)【转】
本文转载自; 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] Buildroot构建指南——根文件系统(Rootfs) Buildroot的Rootfs构建流程有一个大 ...
- CodeForces-451E:Devu and Flowers (母函数+组合数+Lucas定理)
Devu wants to decorate his garden with flowers. He has purchased n boxes, where the i-th box contain ...
- Servlet启动运行顺序
1.web项目执行属性 启动web项目后,web容器首先回去找web.xml文件,读取这个文件. 容器会创建一个 ServletContext ( servlet 上下文),整个 web 项目的所有部 ...
- hdu3709 (平衡数) 数位DP
Balanced Number Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others) ...
- SDK介绍
软件开发工具包(外语首字母缩写:SDK.外语全称:Software Development Kit)一般都是一些软件工程师为特定的软件包.软件框架.硬件平台.操作系统等建立应用软件时的开发工具的集合. ...
- Linux Gcc编译错误(转载)
转自:http://www.linuxidc.com/Linux/2012-01/52153.htm Linux系统下的c编程与Windows有所不同,如果你在用gcc编译代码的时候提示‘for’ l ...
- bzoj 1858: [Scoi2010]序列操作【线段树】
合并中间那块的时候没取max--WAWAWA 在线段树上维护一堆东西,分别是len区间长度,sm区间内1的个数,ll0区间从左开始最长连续0,ml0区间中间最长连续0,rl0区间从右开始最长连续0,l ...
- [Swift]扩展String类:实现find()查找子字符串在父字符串中的位置
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- React实战之将数据库返回的时间转换为几分钟前、几小时前、几天前的形式。
React实战之将数据库返回的时间转换为几分钟前.几小时前.几天前的形式. 不知道大家的时间格式是什么样子的,我先展示下我这里数据库返回的时间格式 ‘2019-05-05T15:52:19Z’ 是这个 ...