今天通过对实验二继承,重载,覆盖的学习,让我更深一步理解了这些概念的区别。

首先来明确一个概念,函数名即地址,也就是说函数名就是个指针。

编译阶段,编译器为每个函数的代码分配一个地址空间并编译函数代码到这个空间中,函数名就指向这个地址空间。

也即每个函数名都有自己唯一的代码空间。

同理,类的成员函数也是如此。

但是,有一点大家一定要记住,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++学习_继承覆盖重载的更多相关文章

  1. IT第十八天 - 类的封装、继承、重载、上周总结★★★

    IT第十八天 上午 封装 1.关键字this,是表示该类在实例化时的对象,即this.表示为该对象的属性 2.类的数据保护,set.get方法的写法规则,为了之后的反射机制的读取数据,set方法中对于 ...

  2. C++学习之继承篇

    今天通过对实验二继承,重载,覆盖的学习,让我更深一步理解了这些概念的区别. 首先来明确一个概念,函数名即地址,也就是说函数名就是个指针. 编译阶段,编译器为每个函数的代码分配一个地址空间并编译函数代码 ...

  3. php继承与重载

    <?php class A { public $param = "paramA"; public function test() { echo "testA&quo ...

  4. python学习_数据处理编程实例(二)

    在上一节python学习_数据处理编程实例(二)的基础上数据发生了变化,文件中除了学生的成绩外,新增了学生姓名和出生年月的信息,因此将要成变成:分别根据姓名输出每个学生的无重复的前三个最好成绩和出生年 ...

  5. java 继承、重载、重写与多态

    首先是java 继承.重载和重写的概念 继承: 继承的作用在于代码的复用.由于继承意味着父类的所有方法亦可在子类中使用,所以发给父类的消息亦可发给衍生类.如果Person类中有一个eat方法,那么St ...

  6. TypeScript学习_入门向

    TypeScript学习_入门向 1-TypeScript简介 首先官网祭天 ---> https://www.tslang.cn/ TypeScript 是 JavaScript 的一个超集, ...

  7. java继承覆盖总结

      Java基础(1)  版权声明:本文为博主原创文章,未经博主允许不得转载. java的继承与覆盖基本是java笔试中常出的题,也比较绕,我这里对java的继承覆盖做一个总结1.构造函数:      ...

  8. Java学习笔记---继承和super的用法

    自从换了个视频教学,感觉比原来那个好多了,就是学校网速太渣,好多视频看一会卡半天,只能先看看已经下载的了. 不过也好,虽然不能从开始开始重新开,但是已经看过一次,在看一次也是好的,就当巩固学习了. 继 ...

  9. Linux操作系统学习_操作系统是如何工作的

    实验五:Linux操作系统是如何工作的? 学号:SA1****369 操作系统工作的基础:存储程序计算机.堆栈(函数调用堆栈)机制和中断机制 首先要整明白的一个问题是什么是存储程序计算机?其实存储程序 ...

随机推荐

  1. Being a Hero (hdu 3251 最小割 好题)

    Being a Hero Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) T ...

  2. #啃underscore源码 一、root对象初始化部分

    最近由于比赛要交了,以及工作室屯了各种项目,实在忙不过来刷题,所以很久没更blog了(良心痛),现在自己的水平还是渣代码堆砌 + 简单的增删改查(悲伤) 所以痛定思痛,决定之后的任务是先补学校课堂的知 ...

  3. openssl动态库编译

    通常Linux系统自带OpenSSL,但是其so文件由于没有debug信息,因此无法跟踪内部函数,对于学习 不太方便,需要通过源码重新安装.         我的Linux系统是CentOS7,自带的 ...

  4. I.MX6 Android can-utils 移植

    /******************************************************************* * I.MX6 Android can-utils 移植 * ...

  5. 并不对劲的bzoj2638

    为了反驳很对劲的太刀流,并不对劲的片手流决定与之针锋相对. 很对劲的太刀流-> 2638: 黑白染色 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit ...

  6. bzoj 3470: Freda’s Walk【拓扑排序+期望dp】

    dfs会T,只好正反两遍拓扑了-- #include<iostream> #include<cstdio> #include<queue> #include< ...

  7. 版本管理工具 Git

    Git是目前世界上最先进的分布式版本控制系统(没有之一). 文章参考来源: https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248 ...

  8. DBMS "无法作为数据库主体执行,因为主体“dbo”不存在、无法模拟这种..........”

    解决方案: 新附加的数据库需要设置所有者才能建立数据库关系图.供参考的操作步骤如下: 选择“AdventureWorks2012LT”,右键,选择“属性”,选择“文件”页,点击“所有者”右侧按钮,点击 ...

  9. linux自动连接校园网设置

    不知道有没有人用linux的时候碰到过校园网连接后,跳不出登录界面,即使手动输入也没有作用.写一个可能可行的方法: - 首先打开控制面板 选择网络代理 将代理中的选项设置为 估计现在就能自动弹出登录页 ...

  10. Retinex系列之Frankle-McCann Retinex 分类: Matlab 图像处理 2014-12-01 21:52 538人阅读 评论(2) 收藏

    一.Frankle-McCann Retinex Frankle-McCann算法选择一条螺旋结构的路径用于像素间的比较.如下图,算法沿着螺旋路径选取用于比较 像素点,这种路径选择包含了整个图像的全局 ...