定义:在类的定义中,函数声明前有virtual关键字的成员函数就是虚函数
如下所示,需要注意的是,关键字virtual只需要在类定义的函数声明中使用,而在描述函数体时不加virtual
class base{
virtual int get();
};
int base::get() { }
注意:构造
虚函数的存在使C++语言中实现多态的前提条件。
虚函数的存在使得C++从基于对象的程序设计语言升级为面向对象的程序设计语言
将派生类的指针赋值给基类指针(指针的类型一定为基类指针,但其取值,是可以为派生类同名虚函数地址的)
关于多态:
通过基类指针调用基类和派生类中的同名虚函数时,
如果指针指向基类对象,则调用基类中的虚函数
如果指针指向派生类对象A,则调用派生类A中的虚函数
如果指针指向派生类对象B,则调用派生类B中的虚函数
即编译器将会调用指针指向的对象中的虚函数,这种机制叫做多态
e.g
class CBase{
public:
virtual void SomeVirtualFunction(){ }
};
class CDerived: public CBase{
public:
virtual void SomeVirtualFunction(){ }
};
int main(){
CDerved ODerived;
CBase * p = & ODerived;
p -> SomeVirtualFunction();// 调用哪个虚函数,取决于p指向哪种类型的对象
return 0;
}
将派生类的对象赋给基类引用
通过基类引用调用基类和派生类中的同名虚函数时,
若该引用 引用的是一个基类的对象,那么被调用的是基类的虚函数
若该引用 引用的是一个派生类的对象,那么被调用的是派生类的虚函数
这也是多态的一种
e.g.
class CBase{
public:
virtual void SomeVirtualFunction(){}
};
class CDerived :public CBase{
public:
virtual void SomeVirtualFunction(){ }
};
int main()
{
CDerived ODerived;
CBase & r = ODerived;
r.SomeVirtualFunction();
//调用哪个虚函数取决于r引用哪种类型的对象
//在这里,显然是使用r指向的CDervied类中的虚函数
return 0;
}
关于多态的使用,其作用体现在:
在面向对象的程序设计中使用多态,能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加的代码较少。
输入若干个几何形体的参数,要求按照面积排序输出,输出时需要指明形状
关于要求:
//几何形体处理程序
//用于计算若干矩形、圆形、梯形等几何图形的面积并排序
# include
# include
# include
using namespace std;
class CShape
{//基类
public:
virtual double Area() = 0;//虚函数等于0而没有函数体,是为纯虚函数
//因为基类CShape没有特定的几何形状与面积,函数无实际意义,故而使用纯虚函数
virtual void PrintInfo() = 0;
};
class CRectangle: public CShape
{//矩形派生类
public:
int w, h;
virtual double Area(){
return w*h;
}
virtual void PrintInfo(){
cout<< "Rectangle:"<< Area()<<endl;
}
};
class CCircle: public CShape
{//圆形派生类
public:
int r;
virtual double Area(){
return 3.14*r*r;
}
virtual void PrintInfo(){
cout<< "Cirle:"<< Area()<<endl;
}
};
class CTriangle:public CShape
{//三角形派生类
public:
int a,b,c;
virtual double Area(){
double p = (a+b+c)/2/0;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
virtual void PrintInfo(){
cout<< "Triangle:"<< Area()<<endl;
}
};
CShape * pShapes[100];//将new出来的派生类对象指针存入基类对象数组
int MyCompare(const void * s1, const void * s2);
//以两个void常指针作为索引,对面积进行比较
int main()
{
int i,n;
CRectangle * pr;
CCircle * pc;
CTriangle * pt;//准备好三类几何形体的指针备用
cin>> n;
for(i=0; i<n; i++){
char c;
cin>> c;
switch(c){
case 'R':
pr = new CRectangle();
cin >> pr->w >> pr->h;
pShapes[i] = pr;//将第i个基类数组指针位,指向矩形派生类对象
break;
case 'C'://同理于矩形
pc = new CCircle();
cin >> pr->r;
pShapes[i] = pc;
break;
case 'T'://同理于矩形和圆形
pt = new CTriangle();
cin >> pt->a >>pt->b >>pt->c;
pShapes[i] = pt;
break;
}
}
qsort(pShapes,n,sizeof(CShape *),MyCompare);
//对基类指针数组pShapes进行排序,共n个元素
//每个元素的大小为sizeof(CShape *),排序方法为MyCompare
for(i= 0; i <n; i++)
pShapes[i] ->PrintInfo();//基类指针pShapes[i],调用基类和派生类均存在的虚函数,是为多态语句
return 0;
}
int MyCompare(const void *s1, const void * s2)
{//对于基类指针数组pShapes元素的排序方法
double a1, a2;//面积
//s1, s2为void *类型,不可以通过*s1方法来得到其指向的内容,
//故而在下文中使用CShape ** 的格式来对s进行格式转换
//关于void *类型的指针,这样编写可以避免计算其指向元素的字节大小
//至于s1所指向的元素,是CShapes数组中的数组元素,数组元素则为CShape的指针
CShape ** p1;
CShape ** p2;
p1 = (CShape **)s1;//强制类型转换
p2 = (CShape **)s2;
a1 = (*p1)->Area();//*p1为基类指针,指向派生类,此语句为多态
a2 = (*p2)->Area();
if (a1<a2)
return -1;
else if(a2<a1)
return 1;
else
return 0;
}
上述程序中涉及到的一个常见处理手法:
用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,对各个派生类对象进行各种操作。
class Base{
public:
void fun1(){this -> fun2();}//"this->"可省略,意义相同
virtual void fun2(){
cout << "Base::fun2()" <<endl;
}
};
class Derived: public Base{
public:
virtual void fun2(){
cout<< "Derived:fun2()" <<endl;
}
};
int main()
{
Derived d;
Base * pBase = &d;//基类指针指向派生类对象
pBase -> fun1();//调用fun1,在其中调用fun2,在fun1中,this指向的指针是派生类对象d,则调用派生类的fun2()
return 0;
}
在非构造函数、非析构函数外的成员函数中调用到了虚函数,则同样是多态
如果在构造函数和析构函数中调用虚函数,则不是多态,多为自己类中对应的虚函数,若该类中未定义,则调用直接基类中的虚函数。
在编译时即可确定
我们通过基类指针指向派生类对象,在使用指针删除对象时,通常情况下由于指针的类别,我们仅仅能够调用基类的析构函数
但是,在我们的设想中,要删除一个派生类对象时,应该先调用派生类的析构函数,然后再调用基类的析构函数。
关于上述问题,我们给出解决办法:将基类的析构函数声明为虚函数
使用情况:
一般来说,一个类如果定义了虚函数,则应该将析构函数也定义为虚函数;或者一个类打算作为基类使用,也应该将析构函数定义成虚函数。
总之,就是涉及到多态问题的类,应该将析构函数设置为虚函数
值得注意的是,析构函数可以是虚函数,但不能以虚函数作为构造函数。
class son{
public:
virtual ~son() {cout << "bye from son"<<endl;}
};
class grandson:public son{
public:
~grandson(){cout << "bye from grandson"<<endl;}//当基类中析构函数声明为虚函数时,派生类虚构函数可再不声明为虚函数
};
int main(){
son * pson;
pson = new grandson();
delete pson;//多态,先调用派生类析构函数,再调用基类析构函数
return 0;
}
/*输出
bye from grandson
bye from son
*/
定义:包含纯虚函数的类叫做抽象类
抽象类只能作为基类排派生新类,不能创建独立的抽象类的对象
但抽象类的指针和引用可以存在,也可以指向由抽象类派生出的新类的对象
在抽象类的成员函数中可以调用纯虚函数,但在其析构函数和构造函数中不能调用纯虚函数
如果一个类从抽象类派生而来,当且仅当它实现了基类中所有纯虚函数(给出具体函数体)时,它才能成为抽象类