虚函数和纯虚函数--最通俗易懂的讲解
上帝是一个程序员,创造了动物(基类),给予了动物吃饭,睡觉,叫唤等通用功能。(封装)
只指定了平均睡觉八小时(虚函数),其中没有指定具体的吃饭,叫唤的行为。(纯虚函数)然后细分一下,动物有猫狗羊和人。(继承)
人类明确它们物种的时候(明确类型的派生类指针)
猫吃鱼 狗吃肉 羊吃草
猫喵喵 狗汪汪 羊咩咩
(多态 同名覆盖)一切都如此顺理成章。
突然人发现一只动物!
这只是什么呢?诶?这货不知道是啥!只能用"动物"来称呼他!(基类指针指向子类对象)当没有虚函数的时候,人类发现这只动物不会叫也不会吃!因为他根本没有这样的实现!(注意:真正编程上如果派生类不对纯虚函数进行实现将无法通过编译)
有了虚函数,让那只动物"吃",发现他吃草!于是捅一下这只动物,发现它会 哞哞叫!于是得知这是一头牛!
可以吃!于是人类就把它吃掉了。
(1)虚函数
虚函数是指在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};含有虚函数的类是虚类。实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数。简单来说虚函数就是实现基类指针调不同子类的同一方法时有不同的行为。
实际上每个含有虚函数的类都有一张虚函数表(vtbl),表中每一项是一个虚函数的地址 。如果继承的父类中有虚函数,那么子类中也会有虚函数表。我们这里只讨论虚函数,推荐一个不错的博客C++虚函数表深入探索(详细全面)。
那么虚函数到底有啥用呢?
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类指针指向其子类的实例,然后通过父类的指针调用子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数;
形成多态必须具备三个条件:
- 必须存在继承关系;
- 继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字Virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);
- 存在基类类型的指针或者引用,通过该指针或引用调用虚函数;
C++支持两种多态性:
- 编译时多态性:通过重载函数实现
- 运行时多态性:通过虚函数实现。
#include<iostream>
using namespace std;
// 基类
class A
{
public:void eat(){printf("1\n");}virtual void sleep(){printf("2\n");}
};
// 派生类
class B : public A
{
public:void eat(){printf("3\n");}void sleep(){printf("4\n");}
};
int main()
{A a;B b;A* p = &a; // 基类指针指向基类对象p->eat(); // 1p->sleep();// 2p = &b; // 基类指针指向子类对象p->eat();// 1p->sleep();// 4return 0;
}
第一个p->eat()和p->sleep()很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。
第二个输出结果就是1、4。p->eat()和p->sleep()则是基类指针指向子类对象,正式体现多态的用法,p->eat()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的eat()函数的代码了,因此输出的结果还是1。
而p->sleep()是基类指针,指向的sleep()是一个虚函数,由于每个虚函数都有一个虚函数表,此时p调用sleep()并不是直接调用函数,而是通过虚函数表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的sleep()函数的地址,因此输出的结果也会是子类的结果4。
通过上面的例子做个总结就是:指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。
什么函数不能声明为虚函数?
- 非类的成员函数(比如友元函数、全局函数)不能定义为虚函数,非类成员函数只能被重载(overload),不能被继承(override),而虚函数主要的作用是在继承中实现动态多态,非成员函数早在编译期间就已经绑定函数了,无法实现动态多态,那声明成虚函数还有什么意义呢?
- 类的成员函数中静态成员函数不能定义为虚函数,因为静态成员函数全局通用,不受限于某个具体对象。
- 构造函数也不能定义为虚函数, (1)构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定 (2) 要想调用虚函数必须要通过“虚函数表”来进行的,但虚函数表是要在对象实例化之后才能够进行调用。而在构造函数运行期间,还没有为虚函数表分配空间,自然就没法调用虚函数。
但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。
(2)纯虚函数
纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能够实例化,但可以声明指向实现该抽象类的具体类的指针或引用。
为啥引入纯虚函数呢?
- 为了方便使用多态特性,我们常常需要在基类中定义虚函数。
- 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数。若要使派生类为非抽象类,则编译器要求在派生类中,必须对纯虚函数予以重写以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。
纯虚函数的意义:让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的默认实现。所以类纯虚函数的声明就是在告诉子类的设计者,"你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它"。
说了这么多,来个实例看看吧:
#include <iostream>
using namespace std;// 抽象类
class Shape
{
public:// 提供接口框架的纯虚函数virtual int getArea() = 0;void setWidth(int w){width = w;}void setHeight(int h){height = h;}
protected:int width;int height;
};// 派生类
class Rectangle: public Shape
{
public:int getArea(){ return (width * height); }
};
class Triangle: public Shape
{
public:int getArea(){ return (width * height)/2; }
};int main(void)
{Rectangle Rect;Triangle Tri;Rect.setWidth(5);Rect.setHeight(7);// 输出对象的面积cout << "Total Rectangle area: " << Rect.getArea() << endl;Tri.setWidth(5);Tri.setHeight(7);// 输出对象的面积cout << "Total Triangle area: " << Tri.getArea() << endl; return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Total Rectangle area: 35
Total Triangle area: 17
从上面的实例中,我们可以看到一个抽象类是如何定义一个接口 getArea(),两个派生类是如何通过不同的计算面积的算法来实现这个相同的函数。
虚函数和纯虚函数--最通俗易懂的讲解相关推荐
- 但并不从包含函数声明的接口派生_C++的虚函数和纯虚函数
虚函数:类成员函数前面添加virtual关键字,则该函数被称为虚函数. 纯虚函数:在虚函数的基础上,在函数末尾加上 = 0. class Animal {public: virtual void Sh ...
- 一口气搞懂《虚函数和纯虚函数》
学习C++的多态性,你必然听过虚函数的概念,你必然知道有关她的种种语法,但你未必了解她为什么要那样做,未必了解她种种行为背后的所思所想.深知你不想在流于表面语法上的蜻蜓点水似是而非,今天我们就一起来揭 ...
- C++知识点51——虚函数与纯虚函数(下)
接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109957146 10.练习 示例 class base { public:base() ...
- C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别
C++ 在继承中虚函数.纯虚函数.普通函数,三者的区别 1.虚函数(impure virtual) C++的虚函数主要作用是"运行时多态",父类中提供虚函数的实现,为子类提供默认的 ...
- 虚函数与纯虚函数的区别
虚函数:为了方便使用多态特性,常常需要在基类中定义虚函数. 纯虚函数: 1.原因与虚函数相同: 2.在很多情况下,基类本身生成的对象是不合理的: 虚函数与纯虚函数的区别: 1.类里声明为虚函数的话,这 ...
- 虚函数和纯虚函数的区别
首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...
- C++ 虚函数和纯虚函数的区别
首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...
- (转)虚函数和纯虚函数区别
在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念.因为它充分体现 了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广.比如在微软的MFC类库中,你 ...
- java中所有函数都是虚函数_关于Java:虚拟函数与纯虚函数之间的区别是什么?...
本问题已经有最佳答案,请猛点这里访问. Possible Duplicate: C++ Virtual/Pure Virtual Explained 虚函数和纯虚函数有什么区别? CPP中的纯虚函数与 ...
- 虚函数和纯虚函数的区别?
虚函数和纯虚函数的区别? 虚函数 引入原因 纯虚函数 引入原因 纯虚函数相当于接口,不能直接实例化,需要派生类来实现函数定义. 虚函数在子类里面也可以不重载的:但纯虚必须在子类去实现 一旦父类的成员函 ...
最新文章
- Spring cloud zuul跨域(一)
- AJAX-jQuery实现Ajax
- SpringBoot 自动开启事务原理
- IOS UIPageControl的设置点为一张图片
- Javascript获取文件自身URL路径
- webapp支持什么数据库_数据库和Webapp安全
- Dinosaur Run - Dinosaur world Games
- css入门之head区设置
- 华为云战略投入政企市场,发布华为云Stack
- java get请求 数组,浅谈vue中get请求解决传输数据是数组格式的问题
- go读取email正文_Go语言库系列之email
- rz、sz (上传下载)命令参数的解释
- 账龄分析表excel模板_老会计分享财务报表及EXCEL会计报表分析系统模板!收藏领取!...
- KVM虚拟化技术(理论知识+搭建虚拟化平台实验步骤)
- 如何处理图片放大后变模糊的情况?
- 获取iOS设备的UDID
- 因为一条SQL,我差点被祭天......,我太难了!
- React- Hook 踩坑“useState“ is called in function “addP“ which is neither a React
- NYOJ 32 组合数
- VMware不支持虚拟化的Intel VT-X/EPT