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

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

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

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

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

但是,有一点大家一定要记住,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. YTU 2945: 编程:五元向量的运算

    2945: 编程:五元向量的运算 时间限制: 1 Sec  内存限制: 128 MB 提交: 151  解决: 85 题目描述 用习惯了的运算符操作新定义的类对象,这是OO方法给我们带来的便利.下面要 ...

  2. maven配置篇

    1,windows A)安装maven之前,确认已正确安装JDK B)下载maven http://maven.apache.org/download.html C)将压缩包解压到指定目录,E:\ap ...

  3. java 防止表单重复提交(serlvet)

    java 防止表单重复提交 ---------FormServlet.java-------------- protected void doGet(HttpServletRequest reques ...

  4. BZOJ4561: [JLoi2016]圆的异或并 计算几何+treap

    因为本题保证两圆之间只有相包含或相离(不用担心两圆重合 因为我没有RE) 所以每个圆之间的相对位置是确定的  也就是可以按极角排序的, 所以可以按横坐标排序后 扫描同时用treap维护加圆删圆(即遇到 ...

  5. Python进程、线程、协程的对比

    1. 执行过程 每个线程有一个程序运行的入口.顺序执行序列和程序的出口.但是线程不能够独立执行,必须依存在进程中,由进程提供多个线程执行控制.每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该 ...

  6. Mybatis中用到的设计模式

    Mybatis中用到至少用到以下设计模式, Builder模式,例如SqlSessionFactoryBuilder.XMLConfigBuilder.XMLMapperBuilder.XMLStat ...

  7. Logistic回归 线性回归(转)

    来源: http://www.cnblogs.com/jerrylead 1 摘要 本报告是在学习斯坦福大学机器学习课程前四节加上配套的讲义后的总结与认识.前四节主要讲述了回归问题,回归属于有监督学习 ...

  8. HDU4418:Time travel(高斯消元+期望)

    传送门 题意 一个人在数轴上来回走,以pi的概率走i步i∈[1, m],给定n(数轴长度),m,e(终点),s(起点),d(方向),求从s走到e经过的点数期望 分析 设E[x]是人从x走到e经过点数的 ...

  9. Linux的远程桌面配置

    一.Ubuntu的远程桌面 Ubuntu默认已安装好VNC服务端组件vino-server,只需要在“系统设置->首选项->桌面共享”中设置即可. 1.设置桌面共享首选项 2.设置好之后, ...

  10. 报错Cannot determine embedded database driver class for database type NONE解决方法

    由于我不需要数据库,启动springboot报错: Cannot determine embedded database driver class for database type NONE If ...