不加virtual,只能访问成员变量,不能访问成员函数
本节阐述C++中的多态(polymorphism)和虚函数(virtual function),首先来阐明一下什么是多态。
#include
using namespace std;
// 基类A
class A{
protected:
int a_A;
public:
//
A(int a):a_A(a){}
void display();
};
void A::display(){
cout<< "this is class A\n"
<<"and value = \n"
<< a_A<<endl;
}
// 派生类 B
class B : public A{
public:
B(int a): A(a){}
void display();
};
void B::display()
{
cout<< "this is class B\n"
<<"and value = \n"
<< a_A<<endl;
}
int main()
{
A *p = new A(1);
p->display();
p = new B(2);
p->display();
return 0;
}
运行结果:
this is class A
and value = 1
this is class A
and value = 2
我们常态思维:如果指针指向了派生类对象,就应该使用派生类的成员变量和成员函数。但是该实例告诉我们,当基类指针p指向派生类B的对象的时候,虽然使用了派生类B的成员变量,但是却没有使用它的成员函数,导致输出结果啥也不是。
也就是说,通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。
为了消除上述尴尬,让基类指针能够访问派生类的成员函数,C++增加了虚函数(Virtual Function)
。使用虚函数非常简单,只需要在函数声明前面增加virtual
关键字。
#include
using namespace std;
class A{
protected:
int a_A;
public:
A(int a):a_A(a){}
virtual void display();
};
void A::display(){
cout<< "this is class A\n"
<<"and value = "
<< a_A<<endl;
}
class B : public A{
public:
B(int a): A(a){}
void display();
};
void B::display()
{
cout<< "this is class B\n"
<<"and value = "
<< a_A<<endl;
}
int main()
{
A *p = new A(1);
p->display();
p = new B(2);
p->display();
return 0;
}
运行结果:
this is class A
and value = 1
this is class B
and value = 2
只需要在函数申明的前面加上virtual
便可以解决上述所说的尴尬。更全面地实现了多态。
C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。
遮蔽关系
的同名函数都将自动成为虚函数。虚析构函数
的必要性析构函数用于在销毁对象时进行清理工作,可以声明为虚函数,且有时候必要声明为虚函数。
#include
using namespace std;
// 基类 A
class A{
protected:
char* str;
public:
A(){
str = new char[100];
cout<<"A contructor"<<endl;
}
~A(){
delete str;
cout<<"A destructor"<<endl;
}
};
// 派生类 B
class B: public A{
private:
char* name;
public:
B(){
name = new char[100];
cout<<"B contructor"<<endl;
}
~B(){
delete name;
cout<<"B destructor"<<endl;
}
};
int main()
{
A *pa = new B();
delete pa;
cout<<"================="<<endl;
B *pb = new B();
delete pb;
return 0;
}
在上述代码中,首先定义了基类A,定义了A的成员变量(protected)str,在构造函数中为str申请一块内存,在析构函数中销毁str的这块内存;定义了A的派生类B,B中定义成员变量name,在B的构造函数中为name申请一块内存,并在B的析构函数中销毁这块内存。 在main函数中,首先定义一个基类A的指针指向派生类B的对象,然后释放这个指针。又定义一个指向派生类的指针指向派生类的对象,再释放该指针。
运行结果:
A contructor
B contructor
A destructor
=================
A contructor
B contructor
B destructor
A destructor
很显然出现了问题:内存泄露
,基类指针指向派生类对象,在释放基类指针的时候,并没有执行派生类的析构函数(没有释放掉派生类中为成员变量name申请的内存空间),只是执行基类的析构函数。
为什么delete pa
; 不会调用派生类的析构函数?
这里的析构函数不是虚函数,通过指针访问非虚函数的时候,编译器会根据指针的类型来确定要调用的函数;也就是说,指针指向哪个类就调用哪个类的函数,。因此pa是基类的指针,不管它指向基类对象还是派生类的对象,始终都只调用基类的析构函数。
为什么delete pb
; 会同时调用派生类和基类的析构函数?
pb是派生类的指针,编译器会根据它的类型匹配到派生类的析构函数,在执行派生类的析构函数的过程中,又会调用基类的析构函数。派生类析构函数始终会调用基类的析构函数,这个过程是隐式完成的。
解决方法:将基类的析构函数声明
为虚函数。
class A{
public:
A();
virtual ~A();
protected:
char* str;
};
运行结果:
A contructor
B contructor
B destructor
A destructor
=================
A contructor
B contructor
B destructor
基类虚析构函数 联动:当基类的析构函数声明为虚函数之后,派生类的析构函数也会自动称为虚函数。此时编译器会忽略指针的类型,而根据指针的指向来选择函数;也就是说,指针指向哪个类就调用哪个类的函数。pa和pb都指向派生类的对象,所以会调用派生类的析构函数,继而再调用基类的析构函数。因此也就解决了内存泄漏问题。
因此多半情况下,基类的析构函数是虚函数
使用了虚函数之后,我们就可以通过定义一个
基类指针,遍历所有的派生类成员变量和成员函数,很大程度简化了代码的复杂度。