多态
概念&作用
- 多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,就是同一种事物表现出的多种形态
- 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。
派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。
静态多态:编译器在编译期间完成,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用哪个函数,如果有对应的函数就调用函数,否则出现编译错误。
- 动态多态(动态绑定):在程序执行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。
动态绑定的条件:
- 在基类中是虚函数(使用virtual关键字修饰的类的成员函数),派生类重写基类的虚函数
- 通过基类的指针或引用调用虚函数
class Base
{
public:
virtual void Funtest()//虚函数
{
cout << "Base::Funtest()" << endl;
}
int _b;
};
class Dervied : public Base
{
public:
virtual void Funtest()//重写
{
cout << "Derived::Funtest()" << endl;
}
int _d;
};
int main()
{
Base b;
b.Funtest();
}
纯虚函数:在成员函数形参列表后写上=0;则成员函数为纯虚函数。
抽象类(接口类):包含纯虚函数的类。
- 抽象类不能实例化出对象
- 抽象类必须有类继承,并且重写纯虚函数,派生类才能实例化出对象。
class Base//抽象类
{
public:
virtual void Funtest() = 0;//纯虚函数
};
继承体系同名函数的关系
虚表分析
注释:所有函数定义省略,内容都为打印该函数名
派生类的虚函数表生成:
class Base
{
public:
virtual void Funtest1();
virtual void Funtest2();
virtual void Funtest3();
int _b;
};
class Dervied : public Base
{
public:
virtual void Funtest1();
virtual void Funtest4();
int _d;
};
int main()
{
Derived d;
//类内没有给出构造函数,编译器自动合成构造函数
//构造函数的作用是将虚表的地址放在前四个字节
return 0;
}
//汇编语句
Dervied d;
01374E48 lea ecx,[d]
01374E4B call Dervied::Dervied (01371500h)
//调用派生类的构造函数
01371500 jmp Dervied::Dervied (013733A0h)
//跳转到派生类的构造函数
Dervied::Dervied:
......
013733C6 call Base::Base (013714F6h)
//在派生类构造函数中调用基类构造函数
......
013714F6 jmp Base::Base (01373360h)
Base::Base:
- 先拷贝基类的虚函数表
- 如果派生类重写了基类的某个虚函数,就替换同位置上的基类虚函数
- 最后跟上派生类自己的虚函数
协变
- 基类中返回基类的引用或指针
- 派生类中返回派生类的引用或指针
class B
{
public:
virtual B& Funtest1();
int _b;
};
class D : virtual public B
{
public:
virtual D& Funtest1();
virtual void Funtest2();
int _d;
};
不能定义为虚函数情况
void Funtest()
{}
class B
{
public:
virtual B();//1 构造函数
virtual static void test();//2 静态成员函数
virtual friend void Funtest();//3 友元函数
virtual B& Funtest1();
int _b;
};
int main()
{
B b;
return 0;
}
//编译器报错(vs2013):
//1.
//error C2633: “B”:“inline”是构造函数的唯一合法存储类
//2.
//error C2216: “virtual”不能和“static”一起使用
//3
// error C2575: “Funtest”: 只有成员函数和基可以是虚拟的
析构函数最好定义为虚函数的情况
- 如果派生类构造函数中申请空间,析构函数中释放空间,析构函数必须定义为虚函数—–防止内存泄漏
最好不要讲operator=定义为虚函数
- 虽然它可以
不要在构造函数和析构函数中调用虚函数
- 对象不完整,可能会出现未定义