提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
首先我们先复习一下C++的三大特性:
- 封装:封装就是将数据和行为(或功能)有机结合起来,形成一个整体。把数据和处理数据的操作结合形成类,数据和函数都是类的成员。增强安全性和简化编程。对外隐藏实现细节暴露公共接口。外部通过接口来调用。
- 继承:继承就是A类被B类继承,A类为父类,B类为子类。B类继承Al类的所有公共和保护成员数据(属性)和成员函数(方法)。子类可以重新定义父类某些属性,重写父类的某些方法,即覆盖父类的某些属性和方法。使其获得与父类不同的功能。
- 多态:一个接口多种实现状态。多态的出现大大提高了程序的扩展性。动态多态,是基于封装和继承的来实现的,多个子类对继承于一个父类的虚函数进行重写,来实现不同状态。静态多态分两种,一种在同一个作用域对函数进行重载(函数名相同,函数参数列表不同),另一种是对函数进行模板化,忽略数据类型强调数据操作。
此时多态便引出了本文的核心:虚函数
情景
当我们希望定义一个基类和多个派生类。他们都具有同样名字的成员函数,但是实现的细节不同,我们希望一个通用函数根据传入类的对象去调用各类函数。此时我们就需要使用虚函数来实现这个要求。
代码如下:
#include<iostream>
using namespace std;
class Base01 //基类Base01
{
public:
void display()const
{
cout<<"Base01 display now"<<endl;
}
};
//派生类Base02
class Base02:public Base01
{
public:
void display()const
{
cout<<"Base02 display now"<<endl; //此时对Base1的display函数进行了重载。
}
};
//公有派生类Derived定义
class Derived:public Base02
{
public:
void display()const
{
cout << "Derived display now" << endl;
}
};
void fun(Base1* ptr) //参数为指向基类对象的指针
{
ptr->display(); //对象指针->成员名
}
int main()
{
Base1 base1; //声明Base1类对象
Base2 base2; //声明Base2类对象
Derived derived; //声明Derived类对象
fun(&base1); //用Base1对象的指针调用fun函数
fun(&base2); //用Base2对象的指针调用fun函数
fun(&derived); //用Derived对象的指针调用fun函数
return 0;
}
我们希望的输出为:
Base01 display now
Base02 display now
Derived display now
但是实际上结果为:
所以,千万不要重新定义继承的非虚函数!
不成功的原因:在编译阶段,编译器根据指针无法判断运行是会指向哪个对象,指针是什么类型的他就会调用对应类型的成员函数,在上面的我们使用的Base01这个类,所以全部都会使用这玩意儿。
使用步骤
既然在编译阶段无法确定要调用的类的函数,可以在运行时再确定——“虚函数”
class Base1//基类Base1定义
{
public:
virtual void display()const
{
cout << "Base01 display now" << endl;
}
};
对基类使用虚函数关键词 virtual 疗效显而易见,结果如下:
总结
用 virtual 关键字说明的函数就是虚函数。
虚函数是实现运行时多态性基础(而静态多态性即编译阶段,则靠重载完成)
C++中的虚函数是动态绑定的函数
虚函数必须是非静态的成员函数,虚函数经过派生之后,就可以实现运行过程中的多态。
虚函数是靠一张虚表来实现的,称为V-Table。这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。
析构函数为什么一定要使用虚函数?
由于类的多态性,基类指针可以指向派⽣生类的对象,如果删除该基类的指针,就会调⽤用该指针指向的派⽣生类析构函数,⽽而派⽣生类的析构函数⼜又⾃自动调⽤用基类的析构函数,这样整个派⽣生类的对象完全被释放。如果析构函数不不被声明成虚函数,则编译器器实施静态绑定,在删除基类指针时,只会调⽤用基类的析构函数⽽而不不调⽤用派⽣生类析构函数,这样就会造成派⽣生类对象析构不不完全,造成内存泄漏。所以将析构函数声明为虚函数是⼗十分必要的。在实现多态时,当⽤用基类操作派⽣生类,在析构时防⽌止只析构基类⽽而不不析构派⽣生类的状况发⽣生,要将基类的析构函数声明为虚函数。
举个例子哈:
#include <iostream>
using namespace std;
class Parent{
public:
Parent(){
cout << "Parent construct function" << endl;
};
//析构函数
~Parent(){
cout << "Parent destructor function" <<endl;
}
};
class Son : public Parent{
public:
Son(){
cout << "Son construct function" << endl;
};
~Son(){
cout << "Son destructor function" <<endl;
}
};
int main()
{
Parent* p = new Son();
delete p;
p = NULL;
return 0;
}
输出为:
将基类的析构函数改为 virtual ~Parent()之后的output为: