一、虚函数

1.如果一个基类希望基类中的某些成员函数在子类中实现子类的自定义版本,就可以将该成员函数定义为virtual

2.当使用基类的指针或引用调用一个虚函数时,将会发生动态绑定(在运行时根据指针或引用的动态类型来决定调用对应动态类型中的虚函数),动态绑定发生时,编译器无法在编译时确定要调用哪个版本的虚函数。如果使用对象来调用成员函数,那么在编译期就能确定要调用的成员函数,因为此时类的静态类型和动态类型是一致的

3.智能指针也支持虚机制

所以,虚函数的实现基础:一是要有类的继承,二是基类指针或引用的动态类型和静态类型有可能不一致

4.如果基类中的成员函数是virtual的,子类经常但不总是会重写该成员函数,如果子类不重写虚函数,子类会直接继承该虚函数,在调用该虚函数时,也依然会发生动态绑定,只不过调用结果类似普通成员函数

5.如果基类中的成员函数是virtual,那么子类继承该基类后,对应的成员函数自动都是virtual的,可以使用virtual重新声明,也可以不使用

示例

class base2
{
public:base2(){cout<<__func__<<endl;}~base2(){cout<<__func__<<endl;}virtual void vfunc1(){cout<<__func__<<"in base2"<<endl;}virtual void vfunc2(){cout<<__func__<<"in base2"<<endl;}virtual void vfunc3(){cout<<__func__<<"in base2"<<endl;}
};class derive2:public base2
{
public:derive2(){cout<<__func__<<endl;}~derive2(){cout<<__func__<<endl;}virtual void vfunc1(){cout<<__func__<<"in derive2"<<endl;}void vfunc2(){cout<<__func__<<"in derive2"<<endl;}
};int main(int argc, char const *argv[])
{base2 *bp=new derive2();shared_ptr<base2> sbp=make_shared<derive2>(derive2());bp->vfunc1();bp->vfunc2();bp->vfunc3();sbp->vfunc1();sbp->vfunc2();sbp->vfunc3();return 0;
}

上述代码通过通过普通指针和智能指针分别指向子类的对象,并通过虚机制分别调用不同版本的虚函数

6.如果子类要重写基类中的虚函数,那么,重写的虚函数的形参列表必须和基类保持一致,返回值一般情况下也要一致。除非该虚函数返回的是该类本身的指针或引用。如果定义了一个和基类虚函数名相同但是形参列表不同的函数,那么这两个函数没有任何关系,子类也没有重写基类中的虚函数。在C++11中,可以在子类中的虚成员函数的形参列表后使用override关键字来表明该成员函数是子类的自定义版本,此时,子类必须重写父类的虚函数,此时如果形参列表不同,编译器将报错

示例

class base2
{
public:base2(){cout<<__func__<<endl;}~base2(){cout<<__func__<<endl;}virtual void vfunc1(int a, double b){cout<<__func__<<"in base2"<<endl;}virtual void vfunc2(int a, double b){cout<<__func__<<"in base2"<<endl;}
};class derive2:public base2
{
public:derive2(){cout<<__func__<<endl;}~derive2(){cout<<__func__<<endl;}virtual void vfunc1(int a, double b){cout<<__func__<<"in derive2"<<endl;}void vfunc2(int a) {cout<<__func__<<"in derive2"<<endl;}
};int main(int argc, char const *argv[])
{shared_ptr<base2> sbp=make_shared<derive2>(derive2());sbp->vfunc1(1, 2.3);sbp->vfunc2(1, 2.3);return 0;
}

因为没有加override,所以基类和子类中的虚函数即使同名,参数列表也可以不同,只不过子类没有重写父类的虚函数而已,两个类中的vfunc2没有任何关系

但是,如果给子类的vfunc2函数添加关键字override,则会报错,因为此时此类必须重写父类的虚函数,但是此时形参列表不同

void vfunc2(int a) override {cout<<__func__<<"in derive2"<<endl;}

7.可以将函数也声明为final,和class类似,如果一个函数是final的,那么,该函数不能在子类中重写

8.虚函数也可以由默认实参,只不过默认实参的是在基类中确定的(因为默认实参是在编译期确定的,是静态绑定,而虚函数是动态绑定,所以默认实参是在基类中确定的)

示例

class base2
{
public:base2(){cout<<__func__<<endl;}~base2(){cout<<__func__<<endl;}virtual void vfunc1(int a, double b=3.3){cout<<__func__<<"in base2"<<endl;cout<<b<<endl;}virtual void vfunc2(int a, double b) {cout<<__func__<<"in base2"<<endl;}
};class derive2:public base2
{
public:derive2(){cout<<__func__<<endl;}~derive2(){cout<<__func__<<endl;}virtual void vfunc1(int a, double b=4.4){cout<<__func__<<"in derive2"<<endl;cout<<b<<endl;}void vfunc2(int a, double b) {cout<<__func__<<"in derive2"<<endl;}
};int main(int argc, char const *argv[])
{shared_ptr<base2> sbp=make_shared<derive2>(derive2());sbp->vfunc1(1, 2.3);sbp->vfunc1(1);sbp->vfunc2(1, 2.3);return 0;
}

可见,即使子类中虚函数的默认实参是4.4,打印出来的默认实参值依旧是基类中的默认实参值

当虚函数使用默认实参,即使基类和子类的默认实参保持一致,也无法保证基类的默认实参不变,一旦基类的默认实参发生变化,所有的子类也要跟着修改默认实参,比较麻烦,所以,当基类中的虚函数使用默认实参时,子类中的虚函数不要重新指定该默认实参

如果将基类中的vfunc2加上final关键字,那么子类将不能对final进行重写

virtual void vfunc2(int a, double b) final {cout<<__func__<<"in base2"<<endl;}

9.即使一个函数是虚函数,并且子类也对其进行了重写,也可以使用作用域运算符回避虚机制。

示例

class base2
{
public:base2(){cout<<__func__<<endl;}~base2(){cout<<__func__<<endl;}virtual void vfunc1(){cout<<__func__<<"in base2"<<endl;}virtual void vfunc2() {cout<<__func__<<"in base2"<<endl;}
};class derive2:public base2
{
public:derive2(){cout<<__func__<<endl;}~derive2(){cout<<__func__<<endl;}virtual void vfunc1(){cout<<__func__<<"in derive2"<<endl;}void vfunc2() {cout<<__func__<<"in derive2"<<endl;}
};int main(int argc, char const *argv[])
{shared_ptr<base2> sbp=make_shared<derive2>(derive2());sbp->base2::vfunc1();sbp->vfunc1();sbp->vfunc2();return 0;
}

如果一个子类的虚函数需要调用基类中的对应的函数,此时如果不使用作用域运算符回避虚机制,将导致无限递归

class base2
{
public:base2(){cout<<__func__<<endl;}~base2(){cout<<__func__<<endl;}virtual void vfunc2() {cout<<__func__<<"in base2"<<endl;}
};class derive2:public base2
{
public:derive2(){cout<<__func__<<endl;}~derive2(){cout<<__func__<<endl;}void vfunc2() {cout<<__func__<<"in derive2"<<endl;shared_ptr<base2> sbp=make_shared<derive2>(derive2());sbp->vfunc2();}
};int main(int argc, char const *argv[])
{shared_ptr<base2> sbp=make_shared<derive2>(derive2());sbp->vfunc2();return 0;
}

第17行因为虚机制会不停的调用子类中的vfunc2,导致无限递归,修改方法就是将动态调用改为静态调用

参考

《C++ Primer》

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

C++知识点50——虚函数与纯虚函数(上)相关推荐

  1. C++知识点51——虚函数与纯虚函数(下)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109957146 10.练习 示例 class base { public:base() ...

  2. 虚函数和纯虚函数详解

    https://mp.weixin.qq.com/s?__biz=MzAxNzYzMTU0Ng==&mid=2651289202&idx=1&sn=431ffd1fae4823 ...

  3. 栈内存 ,堆内存区别 C++ 动态内存 == 与equal区别 复合函数奇偶性 三角函数转换公式: 虚函数和纯虚函数: C++ 中的运算符重载 数据封装,数据抽象 C++ 接口(抽象类

    目录 栈内存 ,堆内存区别 C++ 动态内存 == 与equal区别 复合函数奇偶性 三角函数转换公式: 虚函数和纯虚函数: C++ 中的运算符重载 数据封装,数据抽象 C++ 接口(抽象类): #和 ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最新文章

  1. 在线作图|如何绘制一张好看相关性矩阵图
  2. Java基础-四要素之一《封装》
  3. 【Leetcode | easy】回文数
  4. 如何获取58上真实号码_如何获取Apollo上项目下的所有namespace?
  5. Android图片加载框架之(Glide和Picasso的区别,Glide的简单使用)
  6. 往对象数组里面添加相同的key 不同的value
  7. linux中java 里面启动 重启 停止jar 的 shell
  8. 从一个程序看继承的有关细节及规则(学习马士兵视频的总结)
  9. Unity3D工程源码目录
  10. 希尔密码_密码学中的希尔密码
  11. 计算机有关英语单词以及谐音,英语单词中文谐音大全
  12. Linux系统管理命令之accton的使用
  13. ftp文件/文件夹的上传和下载
  14. ubuntu中自带的ufw防火墙
  15. 华为服务器ip从bios哪里修改,服务器bios设置ip
  16. 中断向量表 异常相量表 中断向量(中断函数入口地址)ARM和X86异常向量表不同
  17. windows c++ 错误汇总
  18. Magix 促销:让你的音视频制作更加专业
  19. 这样设置,让你的 iPhone 用起来更加得心应手
  20. Kotlin之Fragment中直接引用视图控件id

热门文章

  1. [译] PHP7 数组:HashTable
  2. 【优达学城测评】SELECT 子句(6)
  3. ajaxFileUpload文件上传
  4. Building Java Projects with Gradle
  5. 图(1)——图的定义和基本概念
  6. c#导出包含图片的word文档
  7. 使用Latex排版一篇IEEE文章
  8. Numpy生成二项分布随机数
  9. 夜间模式的开启与关闭,父模板的制作
  10. R语言学习笔记(十一):广义线性模型