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++学习_继承覆盖重载的更多相关文章
- IT第十八天 - 类的封装、继承、重载、上周总结★★★
IT第十八天 上午 封装 1.关键字this,是表示该类在实例化时的对象,即this.表示为该对象的属性 2.类的数据保护,set.get方法的写法规则,为了之后的反射机制的读取数据,set方法中对于 ...
- C++学习之继承篇
今天通过对实验二继承,重载,覆盖的学习,让我更深一步理解了这些概念的区别. 首先来明确一个概念,函数名即地址,也就是说函数名就是个指针. 编译阶段,编译器为每个函数的代码分配一个地址空间并编译函数代码 ...
- php继承与重载
<?php class A { public $param = "paramA"; public function test() { echo "testA&quo ...
- python学习_数据处理编程实例(二)
在上一节python学习_数据处理编程实例(二)的基础上数据发生了变化,文件中除了学生的成绩外,新增了学生姓名和出生年月的信息,因此将要成变成:分别根据姓名输出每个学生的无重复的前三个最好成绩和出生年 ...
- java 继承、重载、重写与多态
首先是java 继承.重载和重写的概念 继承: 继承的作用在于代码的复用.由于继承意味着父类的所有方法亦可在子类中使用,所以发给父类的消息亦可发给衍生类.如果Person类中有一个eat方法,那么St ...
- TypeScript学习_入门向
TypeScript学习_入门向 1-TypeScript简介 首先官网祭天 ---> https://www.tslang.cn/ TypeScript 是 JavaScript 的一个超集, ...
- java继承覆盖总结
Java基础(1) 版权声明:本文为博主原创文章,未经博主允许不得转载. java的继承与覆盖基本是java笔试中常出的题,也比较绕,我这里对java的继承覆盖做一个总结1.构造函数: ...
- Java学习笔记---继承和super的用法
自从换了个视频教学,感觉比原来那个好多了,就是学校网速太渣,好多视频看一会卡半天,只能先看看已经下载的了. 不过也好,虽然不能从开始开始重新开,但是已经看过一次,在看一次也是好的,就当巩固学习了. 继 ...
- Linux操作系统学习_操作系统是如何工作的
实验五:Linux操作系统是如何工作的? 学号:SA1****369 操作系统工作的基础:存储程序计算机.堆栈(函数调用堆栈)机制和中断机制 首先要整明白的一个问题是什么是存储程序计算机?其实存储程序 ...
随机推荐
- webService和RMI
1.请求: servlet:提供了请求/响应模式,是JAVA的一种规范,只能使用于java上,用来替代早期使用的难懂的CGI,是一种无状态的请求响应,客户端访问一个服务器的url,只需要发送简单的ht ...
- C++明确规定,不能获取构造函数和析构函数的地址
C++标准明确规定,不能获取构造函数和析构函数的地址,因此也无法形成指向他们的成员函数指针. 指向成员函数的指针可以,指向构造函数析构函数的不行.因为构造函数和析构函数都是没有返回值的,无法声明一个没 ...
- ABAP 检查全角半角
check全角or半角的方法 第一种方法SJIS_DBC_TO_SBC 全角转半角 SJIS_SBC_TO_DBC 半角转换为全角 设定 import all =xtext = 文本全角-〉半角,返回 ...
- VisualSVN Server的配置和使用
VisualSVN Server的配置与使用 本版本为VisualSVN Server 2.7.3版本-不同的版本可能在设置有不同的差异,但都大同小异 1.1启动界面 安装好 VisualSVN Se ...
- YTU 2979: MathBook类--多态
2979: MathBook类--多态 时间限制: 1 Sec 内存限制: 128 MB 提交: 51 解决: 31 题目描述 Book类将自己的display函数设计为虚函数,从而通过父类指针调 ...
- 疯狂JAVA——数组
java是静态语言,java数组也是静态语言 静态初始化: String[] a = new String[]{ "我"}; String[] b = { "你" ...
- Masonry基本用法
使用步骤: 1.导入框架 2.导入头文件,或者直接导入.pch文件中 //省略前缀 'max_'的宏: #define MAS_SHORTHAND // 自动装箱:自动把基本数据类型转化成对象,int ...
- ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(二)数据库初始化、基本登录页面以及授权逻辑的建立
前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...
- hihoCoder 扩展二进制数
明天就要去实验室干活了....下次再打题不知是何时.... 题目链接: http://hihocoder.com/contest/hihointerview11/problem/2 这题不难,一开始想 ...
- bzoj 2754: [SCOI2012]喵星球上的点名【AC自动机】
洛谷90,最后一个点死活卡不过去(也可能是我写的有问题? 比较暴力的做法,把询问带着标号建立AC自动机,用map存儿子. 然后用名字串在自动机上跑,以为是名或姓的子串就行所以把名和姓中间加个特殊字符拼 ...