1,简单介绍

  定义在基类中的函数,子类必须对其进行覆写!【必须对其进行覆写?!】——Java中的接口、Abstract方法中的抽象类也有这样的要求

  C++中定义:

virtual void deal();//子类必须要对这个函数进行覆写

2,主要作用

  (1)定义子类对象,并调用对象中未被子类覆盖的基类函数A。同时在该函数A中,又调用了已被子类覆盖的基类函数B。那此时将会调用基类中的函数B,可我们本应该调用的是子类中的覆盖函数B。虚函数即能解决这个问题。

  ①没有使用虚函数的例子:

#include<iostream>
using namespace std;
class Father //基类 Father
{
public:
void display() {cout<<"Father::display()\n";}
//在函数中调用了,子类覆盖基类的函数display()
void fatherShowDisplay() {display();}
}; class Son:public Father //子类Son
{
public:
//重写基类中的display()函数
void display() {cout<<"Son::display()\n";}
}; int main()
{
Son son; //子类对象
son.fatherShowDisplay(); //通过基类中未被覆盖的函数,想调用子类中覆盖的display函数
}

  输出结果为:Father::display()  

  期望出现的结果应该为:Son::display

  问题出现啦:

  子类Son继承父类Father,并且覆写了父类中的dispaly方法,讲道理我们后续调用子类中的dispaly方法应该想要使用我们覆写的方法;

  但是由于我们调用的父类中未被覆写的fatherShowDisplay方法调用了父类的display方法,所以导致程序调用的时候调用了父类的display方法!

  通俗说法①:我们国家数据局早已经更新【覆写】了国民经济指数,但是你局里员工【内部其他方法】给外人调数据的时候还是调用的以前的老旧数据!!!这样不太合适吧!

  ②使用虚函数的情况,再来看看输出结果

#include<iostream>
using namespace std;
class Father //基类 Father
{
public:
virtual void display() {cout<<"Father::display()\n";}
//在函数中调用了,子类覆盖基类的函数display()
void fatherShowDisplay() {display();}
}; class Son:public Father //子类Son
{
public:
//重写基类中的display()函数
void display() {cout<<"Son::display()\n";}
}; int main()
{
Son son; //子类对象
son.fatherShowDisplay(); //通过基类中未被覆盖的函数,想调用子类中覆盖的display函数
}

  输出结果为:Son::display()

  输出结果是我们期望的结果。

  ③先不查资料,自己在Java中实现上面①中的函数看看输出结果

class Father{
public void display(){
System.out.println("父类显示000000000");
}
public void fatherShowDisplay(){
display();
}
} class Son extends Father{
@Override/
public void display() {
System.out.println("子类显示111111111");
}
} public class Client {
public static void main(String[] args) {
Father father=new Father();
Son son=new Son(); father.fatherShowDisplay();
son.fatherShowDisplay();
}
}

  显示结果【Java没有出现C++中遇到的相关问题!】:

父类显示000000000
子类显示111111111

  (2)在使用指向子类对象基类指针,并调用子类中的覆盖函数时,如果该函数不是虚函数,那么将调用基类中的该函数;如果该函数是虚函数,则会调用子类中的该函数。

  ①有使用虚函数的例子

#include<iostream>
using namespace std;
class Father //基类 Father
{
public:
void display()
{cout<<"Father::display()\n";}
}; class Son:public Father //子类Son
{
public:
void display() //覆盖基类中的display函数
{cout<<"Son::display()\n";}
}; int main()
{
Father *fp; //定义基类指针
Son son; //子类对象
fp=&son; //使基类指针指向子类对象
fp->display(); //通过基类指针想调用子类中覆盖的display函数
}

  输出结果:Father::display()

  果然是调用了父类中的原始方法,没有调用子类覆写的方法

  ②使用虚函数的例子

#include<iostream>
using namespace std;
class Father //基类 Father
{
public:
void virtual display() //定义了虚函数
{cout<<"Father::display()\n";}
}; class Son:public Father //子类Son
{
public:
void display() //覆盖基类中的display函数
{cout<<"Son::display()\n";}
}; int main()
{
Father *fp; //定义基类指针
Son son; //子类对象
fp=&son; //使基类指针指向子类对象
fp->display(); //通过基类指针想调用子类中覆盖的display函数
}

  使用虚函数的输出结果:Son::display()

  确实调用的子类覆写方法

  通俗说法②:现在一名将军【本质上也是士兵】-用手指指着【指针】-士兵【实例化对象】的询问名字【调用方法】,一对父子也在军队中服役,当询问老父亲【父类对象】的名字的时候,父亲向将军报上自己的名字;当询问儿子【子类对象】的名字时,他竟然报了他爹的名字!!!

  ③Java中的例子【Java对象引用-C++指针】

class Father{
public void display(){
System.out.println("父类显示000000000");
}
public void fatherShowDisplay(){
display();
}
} class Son extends Father {
@Override
public void display() {
System.out.println("子类显示111111111");
}
} public class Client {
public static void main(String[] args) {
// Father son=new Son();//这种和下面的其实是一样的,【对象向上转型】 Father father=new Father();
father.fatherShowDisplay(); father=new Son();
father.fatherShowDisplay(); }
}

程序输出:

父类显示000000000
子类显示111111111

  这种情况下,Java依然没有C++虚函数那样的问题!!!

3、实际意义

  通过2-主要作用,他解决的问题知道:虚函数是为了解决子类覆写方法的一致性,一旦覆写后续调用全部都使用最新的方法,不出现调用以前方法的情况!对比一下Java语言中的方法覆写!

 虚函数就是为了解决编程中的多态特性吗!

4,实现原理

  动态连编,在运行的时候确定该调用哪个函数。
  由于子类中存在覆盖函数,相当于该子类对象中有两个函数。那么动态连编也可以解释为,是在定义对象调用构造函数时,将该虚函数与该类绑定在一起。
  基类指针指向基类对象,那调用的肯定是基类虚函数;指向子类对象,那就调用子类虚函数。因为在定义基类或子类对象时,就将虚函数与该类绑定了。

  小知识:

    编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序运行时才能确定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行联编工作被称为动态联编。
  动态联编必须包括以下方面:
    (1)成员函数必须声明为virtual
    (2)如果基类中声明了为虚函数,则派生类中不必再声明。
  调用方式:
    通过对象的指针或引用调用成员函数;或通过成员函数调用,反之就无法实现动态联编。

5,Java中是否存在虚函数???

  首先声明,Java中没有虚函数这个概念、也不存在C++虚函数这样的问题,原因:Java本身【面向对象都有】的三大特性中就有多态这个特性。

  Java的普通函数就相当于C++的虚函数,动态绑定是Java的默认行为【核心:Java没有虚函数的本质原因所在】。

6,Java实现C++遇到的问题

  代码如下:

class Father{
private void display(){//私有化该方法,子类就不能覆写该方法
System.out.println("父类显示000000000");
}
public void fatherShowDisplay(){
display();
}
} class Son extends Father{
public void display() {//这里不是覆写父类方法,而是创建了一个全新的方法
System.out.println("子类显示111111111");
}
} public class Client {
public static void main(String[] args) {
Father father=new Father();
Son son=new Son();
Father fs=new Son();//向上转型 father.fatherShowDisplay();
son.fatherShowDisplay();
son.display(); fs.fatherShowDisplay();
((Son)fs).display();//【强制】向下转型
}
}

  数据输出:

父类显示000000000
父类显示000000000
子类显示111111111
父类显示000000000
子类显示111111111

  这里的方法已经不是覆写了,而是父类的方法被私有化,子类是相当于新建了一个方法。

7,参考链接

https://www.jianshu.com/p/d07e0ac0ba3c?from=singlemessage

https://blog.csdn.net/trojanpizza/article/details/6556604

最新文章

  1. Android必学——AsyncTask
  2. 利用Levenshtein Distance (编辑距离)实现文档相似度计算
  3. 【玩转单片机系列002】 如何使用STM32提供的DSP库进行FFT
  4. ViewPager+Fragment再探:和TAB滑动条一起三者结合
  5. Eclipse IDE for C/C++ Developers安装配置详解
  6. 编写高质量代码--改善python程序的建议(八)
  7. [BS] 小知识点总结-05
  8. git log 常用命令
  9. HackPorts – Mac OS X 渗透测试框架与工具
  10. python模块之hashlib加密
  11. UESTC_In Galgame We Trust CDOJ 10
  12. Servlet的学习之Response响应对象(2)
  13. 蓝桥网试题 java 基础练习 字母图形
  14. python 中 list 的各项操作
  15. tp5命令行基础
  16. Python-HTML 最强标签分类
  17. 深度学习demo
  18. Win10系列:JavaScript页面导航
  19. CreateThread和_beginthread区别及使用
  20. set_union和set_intersection

热门文章

  1. linux设置su和sudo为不需要密码
  2. [Java复习] MQ
  3. 【分类算法】决策树(Decision Tree)
  4. 自己发挥的内容 有关ViewModel的一句翻译(难点expecting)
  5. 【转】地球坐标系 (WGS-84) 到火星坐标系 (GCJ-02) 的转换算法 C#
  6. Django之model补充:一对多、跨表操作
  7. InfluxDB权限认证机制
  8. ubuntu18.04中将刚下载解压的eclipse添加到启动器
  9. charles 工具菜单总结
  10. 【CUDA开发】CUDA编程接口(一)------一十八般武器