(1)多态分为两类:
静态多态:函数重载和运算符重载;
动态多态:派生类和虚函数实现运行时多态。
(2)静态多态和动态多态的区别:
静态多态的函数地址在编译阶段确定(早);
动态多态的函数地址在运行阶段绑定(晚)。
(3)动态多态满足条件:
1.有继承关系;
2.子类重写父类中的虚函数;
3.父类的指针或引用指向子类的对象。
例:
class Animal
{
public:
void speak()
{
cout << "动物说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "猫说话" << endl;
}
};
//地址在编译阶段就确定地址了
void doSpeak(Animal& animal)
{
animal.speak();
}
int main()
{
Cat cat;
doSpeak(cat);//父类的引用接受子类的对象,结果为“动物说话”
return 0;
}
上面的代码,即使传猫的对象,结果也为“动物说话”,是因为函数的地址在编译阶段就确定了,一定会执行animal类里的speak;
如想执行“猫说话”,则函数地址需要在运行阶段绑定,需在 animal 中的 speak 函数前加 virtual。
class Animal
{
public:
virtual void speak()
{
cout << "动物说话" << endl;
}
};
原理:
?Animal 类内部记录了一个虚函数指针,指向一个虚函数表,表中记录了Animal::speak函数地址。
Cat 类内部继承了同样的虚函数指针,当子类重写虚函数,会将表中的父类函数指针覆盖。
在上面的案例中,父类中的虚函数基本没有使用的必要,因此函数内部可以不写内容,将虚函数变为纯虚函数。
纯虚函数写法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类称为抽象类。
抽象类特点:
(1)无法实例化对象
(2)子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
例:
定义一个抽象类
class Base
{
public:
virtual void func() = 0;
};
int main()
{
Base b;//报错,纯虚函数无法实例化对象
}
子类要重写纯虚函数
class Son :public Base
{
public:
virtual void func()
{
cout << "子类重写纯虚函数" << endl;
}
};
问题:多态使用时子类中如果有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构函数,可能会造成内存泄漏。
解决方法:虚析构
例:如果不用虚析构:
class Base
{
public:
virtual void func() = 0;
Base()
{
cout << "Base构造函数调用" << endl;
}
~Base()
{
cout << "Base析构函数调用" << endl;
}
};
class Son :public Base
{
public:
Son(string name)
{
m_name = new string(name);
cout << "Son构造函数调用" << endl;
}
~Son()
{
if (m_name != NULL)
{
cout << "Son析构函数调用" << endl;
delete m_name;
m_name = NULL;
}
}
virtual void func()
{
cout << m_name << endl;
}
string* m_name;
};
int main()
{
Base* base=new Son ("张三");
base->func();
delete base;
}
结果为:
并没有进入Son的析构函数,因此内存泄漏了。
解决:
(1)虚析构:直接在析构函数前加virtual
virtual~Base()
{
cout << "Base析构函数调用" << endl;
}
(2)纯虚析构:在类内声明,类外实现
class Base
{
public:
virtual void func() = 0;
Base()
{
cout << "Base构造函数调用" << endl;
}
virtual~Base() = 0;
};
Base::~Base()
{
cout << "Base纯虚析构函数调用" << endl;
}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。
2.如果子类没有在堆区开辟内存,可以不写虚析构。
3.拥有纯虚析构的类也属于抽象类。