多态定义:
c++
支持多态条件:
1.
在继承状态下,父类指针指向子类对象,通过该指针调用虚函数
2.
父类中存在虚函数,且子类中重写了父类的虚函数
重写:在继承条件下,子类定义了一个与父类虚函数一模一样的函数
多态优点:
解决了继承中的问腿,可以通过父类指针准确调用子类中不统一的函数,且无需使用函数指针
虚函数
-
虚函数指针
-
虚函数列表
虚函数:
普通的函数不会占用类的空间,若在空类中创建一个或多个虚函数,类所占的空间则是
4
个字节
虚函数指针:
__vfptr
(虚函数指针):在一个类中,当存在虚函数时,在定义对象的内存空间的首地址会多分配出一
块内存,在这块内存中增加一个指针变量(二级指针
void**
),也就是虚函数指针。
注意:
1.
属于类的,在编译期存在,与所有成员共享
2.
必须通过真实存在的对象调用,无对象或者空指针对象无法调用虚函数
虚函数调用流程:
1.
定义对象获取对象内存首地址中的
__vfptr
。
2.
间接引用找到虚函数指针指向的虚函数列表
vftable
。
3.
通过下标定位到要调用的虚函数元素(虚函数地址)。
4.
通过这个地址(函数入口地址)调用到了虚函数。
虚函数和普通成员函数的区别:
1.
调用流程不同:虚函数的调用流程相比普通成员函数而言复杂得多,这是他们的本质区别。
2.
调用效率不同:普通的成员函数通过函数名(即函数入口地址)直接调用执行函数,效率高速度快,
虚函数的调用需要虚函数指针
-
虚函数列表的参与,效率低,速度慢。
3.
使用场景不同:虚函数主要用于实现多态,这一点是普通函数无法做到的。
多态实现的原理:
前提:
虚函数列表是属于类的,父类和子类都会有各自的虚函数列表,
vfptr
属于对象,每个对象都有各自的
_vfptr
原理:
1.
由于子类继承父类,不但继承了父类的成员,还会继承父类的虚函数列表
2.
编译器会检查子类是否重写父类的虚函数,如果重写,子类的虚函数会替换掉父类的虚函数,即覆
盖,覆盖后便指向了子类的虚函数
3.
如果子类没有重写父类的虚函数,父类虚函数会保留在子类的虚函数列表
4.
如果子类定义了独有的虚函数,将其添加到子类的虚函数列表末尾
流程:
class CFather {
public:
virtual void fun1() {
cout << __FUNCSIG__ << endl;
}
virtual void fun2() {
cout << __FUNCSIG__ << endl;
}
};
class CSon :public CFather {
public:
virtual void fun1() {
cout << __FUNCSIG__ << endl;
}
virtual void fun3() {
cout << __FUNCSIG__ << endl;
}
void fun4() {
cout << __FUNCSIG__ << endl;
}
};
int main() {
CFather fa;
CSon son;
//定义(new)哪个子类对象,虚函数指针就会指向哪个类的虚函数列表,与哪个指针无关
CFather* pFa = new CSon; //虚函数指针->子类的虚函数列表(而不是父类)
pFa->fun1(); //void __thiscall CSon::fun1(void)
pFa->fun2(); //void __thiscall CFather::fun2(void)
((CSon*)pFa)->fun3(); //void __thiscall CSon::fun3(void)
return 0;
}
虚析构:
问题:
在多态下,父类的指针指向子类的对象,最后在回收空间的时候,是按照父类的类型来回收的,只调用
了父类的析构,子类的析构并没有执行,可能会导致内存泄漏
虚析构解决:
在父类析构函数之前加
~
使其变为虚析构时,调用
pFa
析构会发生多态行为,真正调用的是子类析构,最
后回收对象内存空间时,再调用父类的析构
纯虚函数:
在多态下,有时抽象出来的虚函数作为接口函数,并不知道如何实现或者不需要实现,就是为了多态而
生的,只有继承的子类才知道如何实现,可以把父类的虚函数变为纯虚函数
写法:
在一般虚函数后
=0
特点:
当前类不必实现,而子类必须要重写实现父类的纯虚函数,如果父类的纯虚函数没有被重写,
不
允许
定义子类对象
虚函数实现多态的缺点:
1.
效率问题:调用虚函数效率低,速度慢
2.
空间问题:定义每一个对象都会额外开辟指针大小的空间,虚函数列表占用程序的内存空间,并且会
随着继承的层级递增,占用的空间越来越多
3.
安全问题:通过其他方法可以模拟虚函数的调用,跨过访问修饰符的限制,
私有的函数最好不要变为
虚函数,否则会有安全隐患