静态多态和动态多态–虚函数、纯虚函数
静态多态:程序在编译阶段就可以确定调用哪个函数。这种情况叫做静态多态。比如重载
动态多态:在运行期间才可以确定最终调用的函数。需要通过虚函数+封装+继承实现。
虚函数
1、虚函数都必须有定义
2、虚函数一般用在继承中。多个子类继承同一基类,若在某种行为上不同的派生类有着自己的实现方式。这种情况我们就会用到多态。采用在基类中将此函数定义成虚函数,派生类中定义这个函数的不同实现。当我们使用基类的引用或指针调用一个虚成员函数时会执行动态绑定。因为直到运行的时候才能直到到底调用了哪个版本的虚函数,判断依据是根据引用或指针所绑定的对象的真实类型。
3、若子类中重写了父类的方法,但是父类中此方法并没有设置为虚函数。那么通过指向子类的指针或引用调用此方法的时候,调用的是父类的方法。
4、基类中某个函数一旦被声明为虚函数,则在所有派生类中它都是虚函数,不需要在派生类中再一次通过virtual关键字指出该函数的性质。
5、当且仅当通过指针或引用调用虚函数时,才会在运行时解析该调用。
6、静态成员函数不能被定义成虚函数。因为static成员函数是属于类的。不属于任何对象。
7、内联函数不能被定义成虚函数。因为函数的inline属性是在编译时确定的,而virtual属性是在运行时确定的,因此这个两个属性是不可能同时定义的。
8、构造函数不能被定义成虚函数。
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
cout << "Father Constructor" << endl;
}
void calcMethod()
{
cout << "Father calcMethod()" << endl;
}
virtual void virtualMethod()
{
cout << "Father virtualMethod()" << endl;
}
virtual void virtualCommon()
{
cout << "Father virtualCommon()" << endl;
}
~Father()
{
cout << "Father disConstruct" << endl;
}
};
class Son:public Father
{
public:
Son()
{
cout << "Son Constructor" << endl;
}
void calcMethod()
{
cout << "Son calcMethod()" << endl;
}
void virtualMethod()
{
cout << "Son virtualMethod()" << endl;
}
~Son()
{
cout << "Son disConstruct" << endl;
}
};
int main()
{
Father *f = new Son(); //先执行father构造函数,在执行son构造函数
f->calcMethod(); //Father calcMethod()。父,子--->父。如果父类中的方法有自己的实现,则会去调用父类的方法。 见上述第3条。
f->virtualMethod(); //Son virtualMethod()。父虚,子虚--->子。若把父类中的方法定义成虚函数,子类中有自己的实现,则会去掉用指向的子类中对应的方法。 见上述第2条。
f->virtualCommon(); //Father virtualCommon()。父虚,子无--->父。若把父类中的方法定义成虚函数,子类中有没有覆盖这个虚函数,则会直接调用父类的虚函数。
delete f;
return 0;
}
控制台打印:
Father Constructor
Son Constructor
Father calcMethod()
Son virtualMethod()
Father disConstruct
可以发现调用delete的时候只执行了父类的析构函数,没有执行子类的析构函数。因为父类的析构函数不是虚函数,参参考上文第3点,这不是造成了内存泄漏?怎么解决这个问题呢?
纯虚函数
纯虚函数相当于定义了一个接口,不同的子类必须定义自己的实现。
#include <iostream>
using namespace std;
//抽象类
class Father
{
public:
virtual void calcMem() = 0; //=0表示这是个纯虚函数。纯虚函数不需要定义,没有方法体。
virtual void anotherMethod() = 0; //纯虚函数,也可以定义。
};
void Father::anotherMethod()
{
cout << "Father anotherMethod" << endl;
}
class Son:public Father
{
public:
virtual void calcMem() //这里的virtual也可以不显示声明。
{
cout << "son calcMem" << endl;
}
void anotherMethod()
{
cout << "Son anotherMethod" << endl;
}
};
int main()
{
Son *s = new Son();
s->calcMem(); //son calcMem
s->anotherMethod(); //Son anotherMethod
Father *f = new Son();
f->calcMem(); //son calcMem
f->anotherMethod(); //Son anotherMethod
f->Father::anotherMethod(); //Father anotherMethod。也可以显示的调用父类的方法
return 0;
}
控制台打印:
son calcMem
Son anotherMethod
son calcMem
Son anotherMethod
Father anotherMethod
虚析构函数
delete后面若跟父类指针,则只会执行父类的析构函数。若delete后面跟子类的指针,那么即会执行子类的析构函数,也会执行父类的析构函数。
可以通过在基类中把析构函数定义成虚函数来解决这个问题。因为若不定义成虚函数,通过指向子类的指针或引用调用delete的时候会默认执行父类的析构函数(可参考上述虚函数介绍的第3条),而不会去执行子类的析构函数。
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
cout << "Father Constructor" << endl;
}
virtual ~Father()
{
cout << "Father Destruct" << endl;
}
};
class Son:public Father
{
public:
Son()
{
cout << "Son Constructor" << endl;
}
~Son()
{
cout << "Son Destruct" << endl;
}
};
int main()
{
Father *f = new Son();
delete f;
cout << "--------------" << endl;
Son *s = new Son();
delete s;
return 0;
}
控制台打印
Father Constructor
Son Constructor
Son Destruct
Father Destruct
--------------
Father Constructor
Son Constructor
Son Destruct
Father Destruct
虚函数和纯虚函数的比较
(1)如果基类中定义了虚函数AF,派生类中对于这个虚函数可以覆盖也可以不覆盖。
派生类中如果覆盖了这个虚函数AZ,则通过指向子类的指针或引用调用的就是派生类中AZ
如果派生类中没有对这个AF进行覆盖,那么通过指向子类的指针或引用调用的就是基类中AF
(2)如果基类中定义了纯虚函数AAF,相当于是个接口。那么在派生类中就必须覆盖基类的这个纯虚函数。