一、虚函数基本知识
函数绑定是将函数入口地址和函数调用相联系的过程,分为动态绑定和静态绑定。
C++ 既支持静态绑定,也支持动态绑定。
虚函数是动态绑定的基础,用于类继承关系中,它是在基类中定义的成员函数,而是非静态成员函数。
virtual 函数类型 函数名(参数表);
(1)隐藏:派生类的函数屏蔽了与其同名的基类函数。
(2)重载:同一作用域成员函数被重载
(3)覆盖:派生类函数覆盖基类函数
PS: 为什么在类继承结构中通常将析构函数设置为虚函数?
class A
{
int x;
public:
A(int i) //构造函数
{
}
virtual ~A(){} //析构函数
virtual void display(){}
}
class B:public A //由A 派生B
{
char *buf;
public:
B(int i,char *p):A(i) //构造函数
{
buf = new char[i];
strcpy(buf,p);
}
~B() //析构函数
{
delete buf;
}
void display()
{
A::display();
}
}
主函数:
void main()
{
A *pa = new B(10,"json");
pa -> display();
delete pa;
}
分析:
调用 A::A() # 调用基类
调用 B::B() # 调用派生类,分配buf指向的空间
调用 类B 的display() 成员函数
调用 B::~B() #释放buf 指向的空间
调用 A::~A()
若将程序中的析构函数和display() 该为非虚函数,pa作为类A 对象指针,指向类B对象,但是执行 delete pa 语句时,仅仅调用类A 的析构函数,而不会调用 类B 的析构函数。
在虚函数的多态继承环境中对象指针之间的转换分为3种类型,即子类向基类的向上转换(隐式转换)、基类向子类的向下转换和横向转换。
(1)向上转换
向上转换即将派生类对象指针转换为基类对象指针,向上转换是一种隐式转换,可以直接转换,也可以使用dynamic_cast 运算符。
注意:不能使用基类指针调用派生类中的未重写父类成员函数。
class A
{
public:
virtual void f() {}
}
class B:public A
{
public:
void f() { }
void g() { }
}
void main()
{
B b;
A *pa = &b; //正确,隐式转换,或用 A *pa =dynamic_cast(&b);
pa -> fa() ;//正确,调用类B 的虚函数
pa -> g();//错误,用基类指针调用派生类独有的成员
}
(2)向下转换
向下转换是一种强制转换,这种转换是指对象指针的转换,而不是对象的直接转换,使用dynamic_cast运算符实现向下转换时,若转换失败则换回NULL,若转换成功则返回正常转换后的对象指针。
A *pa = new B;
B *pb = dynamic_cast(pa);
派生类的对象通常大于基类的对象,所以将基类对象指针直接转换为派生类对象指针时,可能会导致不可预见的异常。
A *pa = new A;
B *pb = dynamic_cast(pa);//结果 pb = NULL
(3)横向转换
在多继承层次结构结构中,一个派生类有两个基类,在这两个基类对象指针之间的相互转换称为横向转换。
class A
{
}
class B:public A
{
}
class C:public A
{
}
class D: public B,public C
{
D d;
B *pb = & d;//正确: 隐式向上转换
C *pc = dynamic_cast(pb);//正确的横向转换
}
虚函数表指针:当一个类中有虚函数时,其对象的存储就会在非静态数据成员前面加上一个vfptr 指针,这个指针用来指向一个虚函数表,称为虚函数表指针,vfptr 像数据成员一样存放,占4个字节。
虚函数表:虚函数表中存储着当前类对象的所有虚函数的地址,访问虚函数时,通过vfptr 间址找到vtable表,再进而间址找到要调用的函数。
(1)非虚继承的情况
在没有虚继承的类层次结构中,基类对象和派生类对象的存储组织如下:
(2)虚继承的情况
用的不多,暂省略
c++虚函数实现多态的原理:
若编译器发现一个类中有虚函数表,就会立即为此类生成虚函数表,虚函数表的各表项为指向对应虚函数代码的指针,如果是派生类并重写了虚函数,会用新的重写虚函数的地址代替原来的地址。
虚函数表举例:
纯虚函数是一种特殊的虚函数。包含纯虚函数的类称为抽象类。
虚函数的声明格式
virtual 函数类型 函数名(参数表) = 0;
抽象类是一种特殊的类,其中至少有一个纯虚函数。
抽象类的特点:
c++11中引入了override关键字,被override修饰的函数其派生类必须重载。
【运维经】第45章——marked override, but does not override_夏洛的克-CSDN博客
1.在函数比较多的情况下可以提示读者某个函数重写了基类虚函数(表示这个虚函数是从基类继承,不是派生类自己定义的);
2.强制编译器检查某个函数是否重写基类虚函数,如果没有则报错。
解释
参考文献:
【1】C++:为什么在继承关系中,父类的析构函数最好定义为虚函数?:C++:为什么在继承关系中,父类的析构函数最好定义为虚函数?_来信-CSDN博客_父类析构函数定义为虚函数
【2】C++ 重载、重写(覆盖)、隐藏的定义与区别:C++ 重载、重写(覆盖)、隐藏的定义与区别_YoungYangD的博客-CSDN博客_c++隐藏和覆盖的区别