虚函数&多态

对于经常被问到的虚函数和多态的问题,发现百度百科回答得十分详细,所以自己在百度百科上的解释进行总结

一、虚函数

(1)虚函数简介:在某基类中声明为virtual并在一个或者多个派生类中被重新定义的成员函数;实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。

(2)简单解释:被virtual关键字修饰的成员函数,就是虚函数。

(3)作用:实现多态性(polymorphism)。

(4)过程说明:

  先给定代码,对以下代码做说明:

 1 #include<iostream>
 2 using namespace std;
 3 class Animal
 4 {
 5 public:
 6     void speak()
 7     {
 8         cout<<"I am a animal!"<<endl;
 9     }
10 };
11 class Dog : public Animal
12 {
13 public:
14     void speak()
15     {
16         cout<<"I am a dog!"<<endl;
17     }
18 };
19 int main()
20 {
21     Animal animal;
22     Dog dog;
23     animal.speak();
24     dog.speak();
25     return 0;
26 }

输出结果:

对于上面代码,通过class Animal和class Dog的speak( )函数接口,可以看出这两个class因个体差异而采取不同的行为,但是却没有满足多态性,因为多态性的关键是:一切用指向基类的指针或引用来操作对象。所以我们对main( )函数做修改,采用指向基类的指针或引用来调用:

 1 int main()
 2 {
 3     Animal animal;
 4     Dog dog;
 5     Animal *p1 = &animal;
 6     Animal *p2 = &dog;
 7     p1->speak();
 8     p2->speak();
 9     return 0;
10 } 

 输出结果:

由输出可以看,两个调用输出的都是Animal自己的speak( )函数,这不符合我们的要求,所以引入了虚函数,来解决此问题;因此在基类的成员函数定义前加virtual:

 1 #include<iostream>
 2 using namespace std;
 3 class Animal
 4 {
 5 public:
 6     virtual void speak()
 7     {
 8         cout<<"I am a animal!"<<endl;
 9     }
10 };
11 class Dog : public Animal
12 {
13 public:
14     void speak()
15     {
16         cout<<"I am a dog!"<<endl;
17     }
18 };
19 int main()
20 {
21     Animal animal;
22     Dog dog;
23     Animal *p1 = &animal;
24     Animal *p2 = &dog;
25     p1->speak();
26     p2->speak();
27     return 0;
28 }

 输出结果:

 作为基类的Animal的成员函数speak( )被定义为虚函数,相应的其派生类Dog的成员函数speak( )自动变为虚函数;所以对于派生类中相应成员函数是否加上virtual关键字修饰,是可选的,但是为了可读性,一般还是加上。

(5)限制条件:

  • 非类的成员函数不能定义为虚函数,类的成员函数中静态函数、构造函数也不能定义为虚函数,但是析构函数可以被定义为虚函数;
  • 当基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回类型相关)自动成为虚函数;
  • 如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数;在以该类为基类的派生类中,也不能出现这种同名函数。

(6)总结:

  • 指向基类的指针在操作它的多态对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数;
  • 虚函数联系到多态,多态联系到继承。

二、多态性

  多态是指同一个实体同时具有多种形式。它是面向对象程序设计的一个重要特征。如果一个语言支持类而不支持多态,只能说明它是基于对象的,而不是面向对象的。

  C++中的多态性具体体现在运行和编译两个方面。

  • 运行时,为动态多态,具体引用的对象要在运行时才能确定;
  • 编译时,为静态多态,在编译时就可以确认对象使用的形式。

(1)定义:多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

(2)实现方法:C++中,实现多态可以通过虚函数、抽象类、覆盖、模板(重构与多态无关)。

(3)作用:

  把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。

  赋值之后,父类型的引用就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就是说,父亲的行为像儿子,而不是儿子的行为像父亲。

  举个例子:从一个基类中派生,响应一个虚命令,产生不同的结果。

  比如从某个基类派生出多个子类,其基类有一个虚方法Tdoit,然后其子类也有这个方法,但行为不同,然后这些子类对象中的任何一个可以赋给其基类对象的引用,或者说将子对象地址赋给基类指针,这样其基类的对象就可以执行不同的操作了。实际上你是在通过其基类的引用来访问其子类对象的,你要做的就是一个赋值操作。

  使用继承性的结果就是当创建了一个类的家族,在认识这个类的家族时,就是把子类的对象当作基类的对象,这种认识又叫作upcasting(向上转型)。这样认识的重要性在于:我们可以只针对基类写出一段程序,但它可以适应于这个类的家族,因为编译器会自动找出合适的对象来执行操作。这种现象又称为多态性。而实现多态性的手段又叫称动态绑定(dynamic binding)。

  简单的说,建立一个父类对象的引用,它所指对象可以是这个父类的对象,也可以是它的子类的对象。java中当子类拥有和父类同样的函数,当通过这个父类对象的引用调用这个函数的时候,调用到的是子类中的函数。

三、重构

  一种糟糕的现象:软件产品最初制造出来,是经过精心的设计,具有良好架构的。但是随着时间的发展、需求的变化,必须不断的修改原有的功能、追加新的功能,还免不了有一些缺陷需要修改。为了实现变更,不可避免的要违反最初的设计构架。经过一段时间以后,软件的架构就千疮百孔了。bug越来越多,越来越难维护,新的需求越来越难实现,软件的架构对新的需求渐渐的失去支持能力,而是成为一种制约。最后新需求的开发成本会超过开发一个新的软件的成本,这就是这个软件系统的生命走到尽头的时候。

  重构就能够最大限度的避免这样一种现象。系统发展到一定阶段后,使用重构的方式,不改变系统的外部功能,只对内部的结构进行重新的整理。通过重构,不断的调整系统的结构,使系统对于需求的变更始终具有较强的适应能力。

  重构可以降低项目的耦合度,使项目更加模块化,有利于项目的开发效率和后期的维护。让项目主框架突出鲜明,给人一种思路清晰,一目了然的感觉,其实重构是对框架的一种维护。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏desperate633

设计模式之工厂方法模式(FACTORY METHOD)问题模拟工厂方法模式分析依赖倒置原则小结

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。 我们依然接着简单工厂模式提出的披萨店问题继续探讨

854
来自专栏程序员互动联盟

【编程基础】C++ Primer快速入门五:实用的模板库

除上篇博客介绍的基本数据类型外,C++ 还定义了一个内容丰富的抽象数据类 型标准库。包括 string 和 vector,它们分别定义了字符串和矢量(集合)。s...

2845
来自专栏AI2ML人工智能to机器学习

Python的map函数理解四式

这个map函数是python的内嵌的函数, 那么如何手写一个自己的map函数, 实现内嵌map函数一模一样的功能呢?

1012
来自专栏大数据钻研

如何正确实现Java中的hashCode方法

你知道一个对象的唯一标志不能仅仅通过写一个漂亮的equals来实现 太棒了,不过现在你也必须实现hashCode方法。 让我们看看为什么和怎么做才是正确的。 相...

2749
来自专栏vue学习

函数声明提升与变量提升

1.当在函数的作用域里定义一个和外部变量一样的名称的变量时,变量声明会提升至第一句,但是赋值则不变

852
来自专栏pangguoming

全面理解面向对象的 JavaScript

前言 当今 JavaScript 大行其道,各种应用对其依赖日深。web 程序员已逐渐习惯使用各种优秀的 JavaScript 框架快速开发 Web 应用,从而...

36810
来自专栏AzMark

Python字符串

1095
来自专栏诸葛青云的专栏

C语言夺命题十例,为啥C语言的总是这么恶趣味?

这些问题测试了C语言的高级知识,包括一些很少使用的特性。有效的C编程需要对诸如未定义的行为,递归和指针算术等概念有深入的理解,但是这些故意复杂的例子并不代表现实...

1553
来自专栏菜鸟前端工程师

JavaScript学习笔记021-常用排序算法

512
来自专栏小詹同学

Leetcode打卡 | No.23 合并 k 个有序链表

欢迎和小詹一起定期刷leetcode,每周一和周五更新一题,每一题都吃透,欢迎一题多解,寻找最优解!这个记录帖哪怕只有一个读者,小詹也会坚持刷下去的!

1111

扫码关注云+社区