C++知识点51——虚函数与纯虚函数(下)
接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109957146
10.练习
示例
class base
{
public:base(){cout<<__func__<<endl;}~base(){cout<<__func__<<endl;}virtual void vfunc() {cout<<__func__<<"in base2"<<endl;}
};class derive:public base
{
public:derive(){cout<<__func__<<endl;}~derive(){cout<<__func__<<endl;}void vfunc(int a) { cout<<__func__<<"in derive"<<endl;}virtual void vfunc2() {cout<<__func__<<"in derive"<<endl;}
};class derive2:public derive
{
public:derive2(){cout<<__func__<<endl;}~derive2(){cout<<__func__<<endl;}void vfunc() {cout<<__func__<<"in derive2"<<endl;}void vfunc(int a) { cout<<__func__<<"in derive2"<<endl;}void vfunc2() {cout<<__func__<<"in derive2"<<endl;}
};int main(int argc, char const *argv[])
{base b1;derive d1;derive2 d2;base *pb1=&b1;base *pb2=&d1;base *pb3=&d2;pb1->vfunc();pb2->vfunc();pb3->vfunc();derive *pd1=&d1;derive2 *pd2=&d2;
// bp2->vfunc2();pd1->vfunc2();pd2->vfunc2();base *pb4=&d2;//pb4->vfunc(10);pd1->vfunc(10);pd2->vfunc(10);return 0;
}
上面一个有三个类base,derive,derive2,基类base中只定义了虚函数vfunc,base的子类derive中,定义了vfunc和vfunc2,但是vfunc和基类中的vfunc的形参列表不同,且子类中的vfunc没有override关键字修饰,所以这两个vfunc没有任何关系,而derive的子类derive2中重写了vfunc,且参数和base中的vfunc一致,所以,main中的前三个调用会中的前两个会调用base中的vfunc,而第三个调用会调用derive2中的vfunc
而vfunc2没有在基类base中定义,又因为只能通过静态类型解析能调用的成员,所以不能通过基类的指针调用vfunc2,所以中间三个的第一个调用是错误的,而vfunc2是个虚函数,所以通过中间三个的后两个调用会分别动态调用derive和derive2中的vfunc2
同样,因为基类中没有定义带参数的vfunc,所以不能通过基类的指针调用带参数的vfunc,所以最后三个的第一个调用是错误的,而带参数的vfunc不是虚函数,所以是静态调用,所以最后两个会静态调用derive和derive2中的带参数的vfunc
执行结果如下
上面两个错误的调用也说明,如果基类中没有定义对应的虚函数,只在子类中定义对应的虚函数,那么即使通过基类的指针指向子类,也无法调用子类中的虚函数,无法使用虚机制,因为基类的静态类型无法在基类中找到要调用的成员
11.基类的析构函数通常应该是virtual的
示例
class base3
{
public:base3(){cout<<__func__<<endl;}virtual ~base3(){cout<<__func__<<endl;}
};class derive3:public base3
{
public:derive3(){cout<<__func__<<endl;}~derive3(){cout<<__func__<<endl;}
};int main(int argc, char const *argv[])
{base3 *bp=new derive3();delete bp;return 0;
}
上述代码的输出结果非常正常,但是如果要把base3中的析构函数的virtual关键字去掉,就会变得不正常
从输出结果上看,不仅弹出了了一个警告(销毁含有非虚析构函数的多态类将导致不确定性为),而且还没有调用子类的析构函数,有可能在子类对象析构的时候导致内存泄漏
原因:
1.因为继承的关系,静态类型和动态类型很有可能不一致。此时,delete基类指针时需要正确调用对应版本的析构函数,如果动态类型是基类,那么只调用基类的析构函数即可,此时基类的析构函数是不是虚的无所谓。
2.如果基类指针指向一个动态分配的子类对象,此时,如果基类的析构函数非虚,那么delete 基类指针时,不会触发虚机制,就只会释放子类对象中的基类的部分,导致子类对象销毁不彻底,子类中的成员无法回收,出现内存泄露。如果基类的析构函数是virtual的,那么在delete基类指针销毁子类对象时,会触发虚机制,会先动态调用子类的析构函数,而子类部分销毁后,根据派生类中析构函数的调用顺序,会接着调用基类的析构函数。这样,当基类指针指向一个动态分配的子类对象时,就可以正确的释放动态分配的子类对象。
12.在构造函数和析构函数中调用虚函数
因为一个子类对象在创建时,先创建基类部分,然后再创建子类部分,如果在基类的构造函数中调用虚函数,此时子类的特有部分还没有构建,因此虽然会动态调用虚函数,但是调用的是基类中的虚函数,达不到多态的效果
同理,一个子类对象在销毁时,先销毁子类部分,调用子类的析构函数,然后再销毁基类部分,调用基类的析构函数,如果在基类的析构函数中调用虚函数,此时子类的特有部分已经被销毁,因此虽然会动态调用虚函数,但是调用的是基类中的虚函数,也达不到多态的效果
示例
class base3
{
public:base3(){cout<<__func__<<endl;vfunc2();}virtual ~base3(){cout<<__func__<<endl;vfunc2();}virtual void vfunc2() {cout<<__func__<<"in base2"<<endl;}
};class derive3:public base3
{
public:derive3(){cout<<__func__<<endl;}~derive3(){cout<<__func__<<endl;}void vfunc2() {cout<<__func__<<"in derive3"<<endl;}
};int main(int argc, char const *argv[])
{base3 *bp=new derive3();delete bp;return 0;
}
二、纯虚函数与抽象类
1.纯虚函数在虚函数声明处的最后加个=0就可以将一个虚函数变为纯虚函数,纯虚函数通常不用(但是可以)定义,如果定义的话,需要将纯虚函数定义在类的外部。
2.含有纯虚函数的类就是抽象类。因为含有纯虚函数,所以不能直接定义纯虚类的对象,只能定义纯虚类的指针或者引用,然后指向一个继承该类的子类对象
示例
class animal
{
public:animal():legs(2), wing(false){cout<<__func__<<endl;}virtual ~animal(){cout<<__func__<<endl;}virtual void eat()=0;virtual void drink()=0;virtual void howl()=0;int legs;bool wing;
};void animal::eat()
{cout<<__func__<<endl;
}class duck:public animal
{
public:duck():animal(){cout<<__func__<<endl;}~duck(){cout<<__func__<<endl;}void eat(){cout<<__func__<<"in duck"<<endl;}void drink(){cout<<__func__<<"in duck"<<endl;}void howl(){cout<<__func__<<"in duck"<<endl;}
};int main(int argc, char const *argv[])
{duck d;animal &ra=d;ra.howl();return 0;
}
和虚函数一样,纯虚函数也能实现多态,但是子类通常应该(可以不)实现自定义版本的纯虚函数,否则继承纯虚类后,如果有些纯虚函数没有重新实现,继承到子类里依然是纯虚函数,子类依然是个纯虚类
3.有了虚函数为啥还要纯虚函数
虚函数能实现多态,纯虚函数也能实现多态,那么纯虚函数的作用体现在哪里呢?
除了多态,还有个很重要的原因就是在很多情况下,基类本身生成对象是不合情理的。例如,形状作为一个基类可以派生出三角形,正方形,梯形等,但形状本身生成对象明显不合常理。所以纯虚类一般用来描述定义比较抽象的概念,比如动物,形状,灯,树木等等并定义一堆纯虚函数(接口),然后再由一些表示具体概念的类来继承该抽象类进行具体实现。
参考
《C++ Primer》
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出
C++知识点51——虚函数与纯虚函数(下)相关推荐
- C++ 多态 虚函数与纯虚函数
C++ 多态 虚函数与纯虚函数 虚函数是C++重要思想-多态中不可或缺的一个知识点与用法,但初学者一般很难理解,在这里用通俗语言介绍一下. 百度百科: 在某基类中声明为 virtual 并在一个或多个 ...
- 但并不从包含函数声明的接口派生_C++的虚函数和纯虚函数
虚函数:类成员函数前面添加virtual关键字,则该函数被称为虚函数. 纯虚函数:在虚函数的基础上,在函数末尾加上 = 0. class Animal {public: virtual void Sh ...
- 一口气搞懂《虚函数和纯虚函数》
学习C++的多态性,你必然听过虚函数的概念,你必然知道有关她的种种语法,但你未必了解她为什么要那样做,未必了解她种种行为背后的所思所想.深知你不想在流于表面语法上的蜻蜓点水似是而非,今天我们就一起来揭 ...
- 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中的纯虚函数与 ...
最新文章
- 站内信息 php,站内消息_php教程
- 数字图像处理与python实现 pdf_正版 数字图像处理与Python实现 高等院校计算机科学 人工智能 信号与信息处理 通信工程等专业的...
- POJ2182-Lost Cows【树状数组,二分】
- Pacman主题下给Hexo增加简历类型
- 学习最大流问题推荐几个好的博客
- Android动态添加Device Admin权限
- 生态功能区划方法之三:聚类分析法和生态融合法
- 只有程序员才看得懂的情书
- 云上资源编排1.0到2.0的设计开发思考(含招聘)
- Qt 学习之路 2(3):Hello, world!
- 0xe06d7363怎么解决(0xe06d7363怎么解决DNF)
- Apache监控之MPM配置
- 谷歌SEO考虑富媒体文件
- http 301 302 303 307 308 傻傻分不清
- 10-不用加减乘除做加法
- 2021 强网杯 pwn notebook
- Win10运行LoadRunner11录制脚本无法启动浏览器
- 计算机面试专业英语词汇,面试常用英语,英语面试常用词汇?
- HCIA 交换机原理与ARP协议
- STM32单片机-低功耗设置