多态:(这篇blog总结的是动态的多态的特性)
1、C++中的多态性:
摘自《C++Primer》:引用和指针的静态类型与动态类型可以不同,这是C++用以支持多态性的基石。通过基类引用或指针调用基类中定义的函数时,我们并不知道执行函数的函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类类型的。
如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本
2、多态存在的条件:
1)必须存在继承关系。
2)继承中必须有同名的虚函数且存在覆盖关系。
3)存在基类指针,通过指针调用虚函数。
3、多态的实现原理:
1)当类中声明虚函数时,编译器会在类中生成一个虚函数表。
2)虚函数表是存储类成员函数指针的数据结构。
3)虚函数是由编译器自动生成与维护的,virtual成员函数的地址会被编译器放入虚函数表中。
虚函数:
1.虚函数
被virtual关键字修饰的函数被称为虚函数。若存在继承关系的两个类中有存在覆盖关系方法,则基类中的成员函数为虚函数,则派生类的同名成员方法也自动成为虚函数。但是如果派生类的函数修饰成虚函数的话,基类的函数不能自动成为虚函数。
2.纯虚函数:
在虚函数声明结尾加上 ‘=0’,表明此函数为纯虚函数,默认拥有纯虚函数的类为抽象类,不允许创建对象。原因为因为纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。
3、虚函数指针:
只要我们在类中定义了虚函数,那当我们在定义对象的时候,C++编译器会在对象中存储一个vptr指针,类中创建的虚函数的地址会存放在一个虚函数表中,vptr指针指向了虚函数表的首地址。
虚函数的应用场景:
1、被inline修饰的成员函数可以是虚函数吗?
被inline修饰的成员函数不可以是虚函数。首先,内联是一个静态行为,虚函数是一个动态行为,这两个本身就是矛盾的。当我们用inline来修饰函数时,实际只是向编译器建议该函数可以是内联函数,但是是否内联实际上是编译器说的算的,这就存在一些是内联函数的虚函数。如果函数真的内联,那么会在编译时期就将代码展开,而虚函数是在运行时期才能去调用的。
2、被static修饰的成员函数可以是虚函数吗?
不可以,静态成员函数是没有隐藏的this指针的,是可以不通过对象来调用的。而虚函数一定要通过对象来调用,即有隐藏的this指针。
3、构造函数可以是虚函数吗?
不可以,构造一个对象,可以分为两步:首先,分配一块内存;然后调用构造函数。若构造函数为虚函数,就需要通过vftable来调用函数,而vftable是在构造时才初始化的,这显然也是矛盾的,所以是不可以的。
4、析构函数可以是虚函数吗?
构造函数可以是虚函数且多数时候都需要声明为虚的。
class A{
public:
A()
{
cout<<"A()"<<endl;
}
~A()
{
cout<<"~A()"<<endl;
}
};
class B :public A
{
public:
B()
{
cout<<"B()"<<endl;
}
~B()
{
cout<<"~B()"<<endl;
}
};
int main()
{
A* a =new B;
delete a;
}
输出结果:
A()
B()
~A()
以上例子可以看出,若基类指针指向了一个堆上开辟的派生类对象的话,那么当析构的时候,由于析构函数不是虚函数,则在析构的时候静态绑定调用的是基类的析构函数,导致只析构了基类而没有析构到派生类,造成了内存泄露的问题
class A{
public:
A()
{
cout<<"A()"<<endl;
}
virtual ~A()
{
cout<<"~A()"<<endl;
}
};
class B :public A
{
public:
B()
{
cout<<"B()"<<endl;
}
~B()
{
cout<<"~B()"<<endl;
}
};
int main()
{
A* a =new B;
delete a;
}
输出结果:
A()
B()
~B()
~A()
Program ended with exit code: 0
若我们将基类的析构函数修饰成虚的,那么当我们调用虚函数时会动态绑定确定最终调用的是派生类的析构函数,派生类析构函数先析构自己,再析构继承来的基类。避免了内存泄露的问题。
所以析构函数是可以为虚函数的且当存在继承多态关系的时候最好声明为虚函数。