一、虚函数

1.虚函数的概念

1.虚函数就是在基类中被关键字 virtual 说明,并在派生类中重新定义的函数。

2.虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

3.虚函数的定义是在基类中进行的,它是在基类中在那些需要定义为虚函数的成员函数的声明中冠以关键字 virtual 。

定义虚函数的方法如下:

virtual 函数类型 函数名(形参表){函数体;
}

总而言之,虚函数是在编译时,并不能确定的类函数,而是在运行时确定的。

核心点:通过基类对象访问派生类实现的函数。

2.虚函数的例子

虚函数的例子,通常有三步。

  • 第一步,定义基类,声明基类函数为 virtual 的。

  • 第二步,定义派生类(继承基类),派生类实现了定义在基类的 virtual 函数。

  • 第三步,声明基类指针,并指向派生类,调用virtual函数,此时虽然是基类指针,但调用的是派生类实现的基类virtual 函数。

// 例子来源于: 菜鸟教程
class A
{public:virtual void foo(){cout<<"A::foo() is called"<<endl;}
};class B:public A
{public:void foo(){cout<<"B::foo() is called"<<endl;}
};int main(void)
{A *a = new B();a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!return 0;
}

3.纯虚函数

纯虚函数与虚函数的区别在于,纯虚函数的基类中的virtual函数,只定义了,但不实现。实现交给派生类来做。

PS:带纯虚函数的类也叫抽象类,因为这种基类不能直接生成对象。

优点:

  • 防止派生类忘记实现虚函数,纯虚函数使得派生类必须实现基类的虚函数。

  • 在某些场景下,创建基类对象是不合理的,含有纯虚拟函数的类称为抽象类,它不能生成对象。

声明方法

在基类中纯虚函数的方法的后面加 =0:

// 例子来源于: 菜鸟教程
virtual void funtion()=0

4.虚函数的使用

例1:

#include<iostream>
using namespace std;
class B0{public:virtual void print(char *p){    //定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B1:public B0{public:virtual void print(char *p){  //重新定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B2:public B1{public:virtual void print(char *p){  //重新定义虚函数 print cout<<p<<"print()"<<endl;}
};
int main(){B0 ob0,*op;  //定义基类对象 ob0 和对象指针 opop=&ob0; op->print("B0::");    //调用基类 B0 的 print B1 ob1;  //定义派生类 B1 的对象 op=&ob1;op->print("B1::");  //调用派生类 B1 的 print B2 ob2;op=&ob2;op->print("B2::");return 0;
}

执行结果:

说明:

(1)若在基类中,只声明虚函数原型(需加上 virtual),而在类外定义虚函数时,则不必再加 virtual。例如:

class B0{public:virtual void print(char *p);     //声明虚函数原型,需加上 virtual
};

在类外,定义虚函数时,不要加 virtual:

void B0::print(char *p){cout<<p<<"print()"<<endl;
}

(2)在派生类中,虚函数被重新定义时,其函数的原型与基类中的函数原型(即包括函数类型、函数名、参数个数、参数类型的顺序)都必须完全相同。
(3)C++ 规定,当一个成员函数被定义为虚函数后,其派生类中符合重新定义虚函数要求的同名函数都自动称为虚函数。因此,在派生类中重新定义该虚函数时,关键字 virtual 可以不写。 但是,为了使程序更加清晰,最好在每一层派生类中定义该函数时都加上关键字 virtual。
(4)如果在派生类中没有对基类的虚函数重新定义,则公有派生类继承其直接基类的虚函数。一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。 例如:

class B0{···public:virtual void show();  //在基类定义 show 为虚函数
};
class B1:public B0{···
};

若在公有派生类 B1 中没有重新定义虚函数 show ,则函数 show 在派生类中被继承,仍是虚函数。
(5)虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。
(6)使用对象名和点运算符的方式调用虚函数是在编译时进行的,是静态联编,没有利用虚函数的特性。只有通过基类指针访问虚函数时才能获得运行时的多态性。
例 2:使用对象名和点运算符的方式调用虚函数

#include<iostream>
using namespace std;
class B0{public:virtual void print(char *p){    //定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B1:public B0{public:virtual void print(char *p){cout<<p<<"print()"<<endl;}
};
class B2:public B1{public:virtual void print(char *p){cout<<p<<"print()"<<endl;}
};
int main(){B0 ob0;ob0.print("B0::");B1 ob1;ob1.print("B1::");B2 ob2;ob2.print("B2::");return 0;
}

5.虚析构函数

我们在动态分配堆上内存的时候,析构函数必须是虚函数

原因:动态分配堆上内存,无法自动回收。若基类指针指向派生类,然后基类指针调用delete方法,只能释放基类的内存,无法释放派生类特有的部分内存,进而导致内存泄露。

析构函数定义成虚函数,基类指针调用delete方法,会先调用派生类的析构函数,然后自动调用基类的析构函数。

所以,将可能被继承的基类的构造函数设置为虚函数,可以防止用基类指针指向子类是,释放基类指针是可以释放掉子类独有的空间,进而防止内存泄漏。

例1

#include<iostream>
using namespace std;
class B{public:~B(){cout<<"调用基类 B 的析构函数\n";}
};
class D:public B{public:~D(){cout<<"调用派生类 D 的析构函数\n";}
};
int main(){D obj;return 0;
}

这段程序会先执行派生类的析构函数,再执行基类的析构函数。但是,如果在主函数中用 new 运算符建立一个派生类的无名对象和定义了一个基类的对象指针,并将无名对象的地址赋给这个对象指针。当用 delete 运算符撤销无名对象时,系统只执行基类的析构函数,而不执行派生类的析构函数。

例2:虚析构函数的使用

#include<iostream>
using namespace std;
class B{public:virtual ~B(){cout<<"调用基类 B 的析构函数\n";}
};
class D:public B{public:virtual ~D(){cout<<"调用派生类 D 的析构函数\n";}
};
int main(){B *p;    //定义指向基类 B 的指针变量 pp=new D;
//用运算符 new 为派生类的无名对象动态地分配了一个存储空间,并将地址赋给对象指针 pdelete p;
//用 delete 撤销无名对象,释放动态存储空间return 0;
}

测试结果:

由于使用了虚析构函数,程序执行了动态联编,实现了运行的动态性。虽然派生类的析构函数与基类的析构函数名字不相同,但是如果将基类的析构函数定义为虚函数,由该基类所派生的所有派生类的析构函数也都自动成为虚函数。

二、虚函数表

虚函数表是由有虚函数的类生成的,简称为 V-Table

虚函数表由编译器生成,如果一个类有虚函数,那么该类就会生成一个4个字节的虚函数表指针,指向虚函数表。指针存储在对象实例的最前面位置。

虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址(这个概念与我们前面学习的deque的中控器有些相似,可以分段管理duque中使用的分段连续线性空间)

1.单继承下的虚函数表

派生类直接继承基类虚函数

下图展示了一个派生类继承基类虚函数,且没有重写基类的虚函数,对应的虚函数指针、虚函数表、和虚函数表对应指针调用的方法。可以看出:

  • 虚函数表中的指针顺序,按照虚函数声明的顺序。

  • 基类的虚函数指针在派生类的前面。

 派生类重写基类虚函数

下图展示了一个派生类重写基类虚函数,且重写基类的部分虚函数,对应的虚函数指针、虚函数表、和虚函数表对应指针调用的方法。可以看出:

虚函数表中,派生类重写的虚函数替换了基类虚函数指针,并指向了派生类的函数实现。

2.多继承下的虚函数表

多继承下的虚函数表,还是只有一个虚函数表。

多个基类之间的虚函数,按照继承的顺序,存放虚函数指针。

基类内部的虚函数,按照虚函数内部声明的顺序存放。

派生类直接继承基类虚函数

下图展示了多继承下的虚函数表,派生类直接继承基类虚函数,类似与单继承下的虚函数表的情况。

区别在于:

  • 多个基类之间的虚函数,按照继承的顺序,存放虚函数指针。

  • 基类内部的虚函数,按照虚函数内部声明的顺序存放。

 派生类重写基类虚函数

下图展示了多继承下的虚函数表,派生类重写部分基类虚函数,类似与单继承下的虚函数表的情况。

区别在于:

  • 多个基类之间的虚函数,按照继承的顺序,存放虚函数指针。

  • 基类内部的虚函数,按照虚函数内部声明的顺序存放。

  • 虚函数表中,派生类重写的虚函数替换了基类虚函数指针,并指向了派生类的函数实现。

这就是C++虚函数和虚函数表的部分内容了,相信通过今天的学习,我们一定能对虚函数和虚函数表有着更加深刻的理解。

C++ 虚函数和虚函数表相关推荐

  1. c++虚函数和虚函数表

    前言 (1)虚基表与虚函数表是两个完全不同的概念 虚基表用来解决继承的二义性(虚基类可以解决). 虚函数用来实现泛型编程,运行时多态. (2)虚函数是在基类普通函数前加virtual关键字,是实现多态 ...

  2. C++虚函数及虚函数表解析

    一.背景知识(一些基本概念) 虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数. 纯虚函数(Pure Virtual Functi ...

  3. 【虚函数指针 虚函数表】

    文章目录 虚函数指针和虚函数表 1.虚函数的含义 2.虚函数的作用 3.虚函数的实现原理 多态的实现原理 `普通类` `当类中存在虚函数` `子类继承父类不重写虚函数` 子类继承父类重写虚函数 1.虚 ...

  4. 【C++】虚函数与虚函数表

    1. 虚函数表的结构 #include <iostream> using namespace std;typedef void (*Fun)(void);class Base {publi ...

  5. 虚函数与纯虚函数以及虚函数表之间的关系

    1.虚函数 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.C++中虚函数的作用主要是实现多态机制.所谓多态就是用父类指针指向子类对象,然后通过父类指针调用实际子类的成员函数,这种技术 ...

  6. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    文章目录 静态多态.动态多态 虚函数 哪些函数类型不可以被定义成虚函数? 虚函数的访问方式 析构函数中的虚函数 虚函数表指针 vptr 多继承下的虚函数表 虚基类表指针 bptr 纯虚函数 抽象类 虚 ...

  7. C++虚函数和虚函数表原理

    虚函数的地址存放于虚函数表之中.运行期多态就是通过虚函数和虚函数表实现的. 类的对象内部会有指向类内部的虚表地址的指针.通过这个指针调用虚函数. 虚函数的调用会被编译器转换为对虚函数表的访问: ptr ...

  8. C++虚函数,虚函数表,虚继承,虚继承表

    一.虚函数 类中用virtual关键字修饰的函数. 作用:主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的 ...

  9. C++中虚函数、虚指针和虚表详解

    关于虚函数的背景知识 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数. 存在虚函数的类都有一个一维的虚函数表叫做虚表.每一个类的对象都有一个指向虚表开始的虚指针.虚表是和类对应的 ...

  10. c++ map 析构函数_C++|类继承关系中的虚函数、虚析构函数、虚基类

    在继承关系中,虚函数.虚析构函数.虚基类中使用的关键字virtual都是在告诉编译器,此处要进行特殊处理: 虚函数:函数重写时的要求编译器动态绑定来实现多多态 : 虚析构函数:当基类指针指向在堆内实现 ...

最新文章

  1. 谢文睿:西瓜书 + 南瓜书 吃瓜系列 9. 集成学习(上)
  2. 本科生顶刊发封面文章!他,是能成就导师的学生
  3. 【从零学习OpenCV 4】opencv_contrib扩展模块的安装
  4. javascript 面向对象编程(工厂模式、构造函数模式、原型模式)
  5. ETPS英文文本处理软件
  6. activemq和mysql_activeMQ 填坑记
  7. 如何在三个月内获得三年的工作经验
  8. 第二章 获取变量的相关统计指标
  9. MySql 你知道什么情况下适合使用Join 联表查询吗 ?
  10. Mysql学习总结(28)——MySQL建表规范与常见问题
  11. 如何为自己赢得更好的口碑
  12. 大学数学学习参考书点评
  13. android nv21 nv12,直接进行nv21或者nv12的resize
  14. HTML页面 引用js文件中文乱码
  15. duliu题之狼抓兔子题解
  16. Redis的scan命令
  17. 如何使用小米手机对文档进行扫描
  18. 4个角度教你选小程序开发工具?
  19. 如何在Word中创建和打印标签
  20. apple id是什么意思

热门文章

  1. 手机图标ui设计尺寸:ui设计app图标尺寸规范
  2. .net微信开发吐血总结
  3. 大数据与AI平台:物联网时代的数据智能 PPT分享
  4. Revit二次开发——选集
  5. 工业智能网关,数据采集网关
  6. hikaricp mysql_JAVA连接数据库 #03# HikariCP
  7. 一本通1665【例 3】移棋子游戏
  8. KMS激活工具 HEU_KMS_Activator_CH_v7.8.4
  9. adobe acrobat pro dc 无法打开PDF_pdf怎样转成word格式
  10. vs.Net 2003 安装