在函数定义中有vitural关键字的函数就是虚函数,不能用在构造函数和静态函数上面。
虚函数只能借助于指针或者引用来达到多态的效果。
#include <iostream>
#include <cstring>
using namespace std;
class A {
private:
string name;
public:
A(string n):name(n) {
}
string getName() {
return name;
}
void print() {
cout<<"A"<<endl;
}
virtual void hutrted() {
cout<<name<<" get hurted"<<endl;
}
virtual void attack(A* a) {
cout<<name<<"attack " << a->getName()<<endl;
a->hutrted();
}
};
class B:public A{
public:
B(string n):A(n) {
}
void hutrted() {
A::hutrted();
cout<<"this is b override hutrted"<<endl;
}
void attack(A* a) {
A::attack(a);
cout<<"this is b override attack"<<endl;
}
};
int main() {
A* a = new A("A");
A* b = new B("B");
b->attack(a);
cout<<"---------------------"<<endl;
a->attack(b);
delete a;
delete b;
return 0;
}
在上面的例子中就实现了多态,如果将基类A 方法中的virtual关键字去掉,那么这个时候将无法实现多态。
虚函数的作用在于实现了动态关联,即一个类的函数调用在编译的时候并不能确定而需要在程序运行的时候决定。
如果将A 中的virtual关键字去掉,那么函数的调用在编译的时候即决定。无法实现多态。
与java对比的话,在java当中只需要通过继承并且重写父类的方法即可实现多态
在构造函数和析构函数中不会形成多态,因为这个是在编译期间就确定了其行为。在构造函数和析构函数中调用虚函数,调用的是本类中定义的函数(如果没有定义就从父类中去找)。之所以这么设计是因为如果在父类的构造器中调用了虚函数,在构造子类的时候会先调用父类的构造器这个时候虚函数如果是多态,那么会调用子类的方法,但是这个时候子类还没有生成对象,这个时候调用会产生错误,析构函数同理,子类对象会先于父类对象被析构。这个时候如果在父类的析构函数中调用了虚函数,即在一个已经析构的对象上调用方法,也会产生错误
虚析构函数
看下面的这段代码
class Super{
public:
~Super(){
cout<<"~Super invoked"<<endl;
}
};
class Son : public Super {
~Son(){
cout<<"~Son invoked"<<endl;
}
};
int main() {
Super* son = new Son;
delete son;
return 0;
}
当我们动态的分配内存空间给son的时候,如果使用delete删除掉son,这个时候只会调用Super的析构函数,这个时候Son的构造函数并没有得到调用,那么这就有可能造成内存泄露,为了解决这种问题,就出现了虚析构函数
class Super{
public:
virtual~Super(){
cout<<"~Super invoked"<<endl;
}
};
class Son : public Super {
~Son(){
cout<<"~Son invoked"<<endl;
}
};
int main() {
Super* son = new Son;
delete son;
return 0;
}
即在在父类的析构函数前加上virtual关键字。这样当我们再次调用delete son的时候,会先调用Son的析构函数在调用Super的析构函数。
C++多态实现的原理
在C++当中,如果一个类中包含了虚函数,那么这个类中就会存在虚函数表。并且该类的任何对象中都存在这虚函数表的指针
class Super{
public:
int a;
virtual~Super(){
cout<<"~Super invoked"<<endl;
}
};
所以当我们输出sizeof(Super)的时候,会输出16(在64位的dev C++中),这就说明了函数表中存在其他的内容,而这个内容就是虚函数表,多态的函数调用语句被编译成一系列根据基类指针所指向对象中存放的虚函数表的地址,在虚函数表中查找虚函数的地址,并调用虚函数的指令。
纯虚函数和抽象类
纯虚函数是指没有函数体的虚函数,那么包含了纯虚函数的类就是抽象类了,抽象类不能生成对象(但是可以使用抽象的指针指向基类),只能作为派生类的基类来使用,并且在类的构造方法和析构函数中不能调用纯虚函数。
一个子类只有实现了父类当中定义的所有纯虚函数才能变成非抽象类,否则还是一个抽象类。
class Super{
public:
int a;
virtual~Super(){
cout<<"~Super invoked"<<endl;
}
virtual void method() = 0; //纯虚函数
};
class Son : public Super {
int b;
~Son(){
cout<<"~Son invoked"<<endl;
}
void method() { //纯虚函数实现
}
};
int main() {
Super* son = new Son;
delete son;
return 0;
}