接上一篇文章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——虚函数与纯虚函数(下)相关推荐

  1. C++ 多态 虚函数与纯虚函数

    C++ 多态 虚函数与纯虚函数 虚函数是C++重要思想-多态中不可或缺的一个知识点与用法,但初学者一般很难理解,在这里用通俗语言介绍一下. 百度百科: 在某基类中声明为 virtual 并在一个或多个 ...

  2. 但并不从包含函数声明的接口派生_C++的虚函数和纯虚函数

    虚函数:类成员函数前面添加virtual关键字,则该函数被称为虚函数. 纯虚函数:在虚函数的基础上,在函数末尾加上 = 0. class Animal {public: virtual void Sh ...

  3. 一口气搞懂《虚函数和纯虚函数》

    学习C++的多态性,你必然听过虚函数的概念,你必然知道有关她的种种语法,但你未必了解她为什么要那样做,未必了解她种种行为背后的所思所想.深知你不想在流于表面语法上的蜻蜓点水似是而非,今天我们就一起来揭 ...

  4. C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别

    C++ 在继承中虚函数.纯虚函数.普通函数,三者的区别 1.虚函数(impure virtual) C++的虚函数主要作用是"运行时多态",父类中提供虚函数的实现,为子类提供默认的 ...

  5. 虚函数与纯虚函数的区别

    虚函数:为了方便使用多态特性,常常需要在基类中定义虚函数. 纯虚函数: 1.原因与虚函数相同: 2.在很多情况下,基类本身生成的对象是不合理的: 虚函数与纯虚函数的区别: 1.类里声明为虚函数的话,这 ...

  6. 虚函数和纯虚函数的区别

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...

  7. C++ 虚函数和纯虚函数的区别

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...

  8. (转)虚函数和纯虚函数区别

    在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念.因为它充分体现 了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广.比如在微软的MFC类库中,你 ...

  9. java中所有函数都是虚函数_关于Java:虚拟函数与纯虚函数之间的区别是什么?...

    本问题已经有最佳答案,请猛点这里访问. Possible Duplicate: C++ Virtual/Pure Virtual Explained 虚函数和纯虚函数有什么区别? CPP中的纯虚函数与 ...

最新文章

  1. 站内信息 php,站内消息_php教程
  2. 数字图像处理与python实现 pdf_正版 数字图像处理与Python实现 高等院校计算机科学 人工智能 信号与信息处理 通信工程等专业的...
  3. POJ2182-Lost Cows【树状数组,二分】
  4. Pacman主题下给Hexo增加简历类型
  5. 学习最大流问题推荐几个好的博客
  6. Android动态添加Device Admin权限
  7. 生态功能区划方法之三:聚类分析法和生态融合法
  8. 只有程序员才看得懂的情书
  9. 云上资源编排1.0到2.0的设计开发思考(含招聘)
  10. Qt 学习之路 2(3):Hello, world!
  11. 0xe06d7363怎么解决(0xe06d7363怎么解决DNF)
  12. Apache监控之MPM配置
  13. 谷歌SEO考虑富媒体文件
  14. http 301 302 303 307 308 傻傻分不清
  15. 10-不用加减乘除做加法
  16. 2021 强网杯 pwn notebook
  17. Win10运行LoadRunner11录制脚本无法启动浏览器
  18. 计算机面试专业英语词汇,面试常用英语,英语面试常用词汇?
  19. HCIA 交换机原理与ARP协议
  20. STM32单片机-低功耗设置

热门文章

  1. WinDbg配置和使用基础
  2. 第二课unit2 控制对文件的访问
  3. Bzoj1002 [FJOI2007]轮状病毒
  4. [WIKIOI1298]凸包周长[裸凸包]
  5. python正态分布相关函数
  6. java基础相关知识
  7. Jzoj3806 小X 的道路修建
  8. JS小数点加减乘除运算后位数增加的解决方案
  9. 常用网站--前端开发类+网页设计类+平面素材类+flash类
  10. MFC List Control 控件添加单元格编辑,实现可编辑重写