C++虚函数虚析构函数纯虚函数的使用说明和理解
一、为什么要使用虚函数
请看下列代码:
#include <iostream>
#include <string>
using namespace std;class Student{
public:Student(int n, string na, float s):num(n),name(na),score(s){}void display( );//virtual void display( );//此时基类的成员函数被定义为虚函数!!!!
protected:int num;string name;float score;
};class Graduate:public Student
{
public:Graduate(int n, string na, float s, float p):Student(n,na,s),pay(p){}void display( );
private:float pay;
};void Student::display()
{cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\n\n";
}void Graduate::display()
{cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;
}int main( )
{Student stud1(1001,"Li",87.5);Graduate grad1(2001,"Wang",98.5,563.5);Student *pt = &stud1;//注意:此处pt由基类定义,指向基类对象的指针pt->display();pt = &grad1;pt->display();Graduate *pt1 = &grad1;//注意:此处定义pt1指向派生类对象pt1->display();return 0;
}
运行结果如下:
从以上例子可看出,由于定义了pt为指向基类Student的指针,因此该指针只能访问基类成员,而不能访问派生类成员。
但是当基类的成员函数display被定义为虚函数呢?
virtual void display( );
运行结果如下:
由上可知,当基类的成员函数被定义为虚函数时,即便指针指向基类对象,但是当赋值派生类给当前指针时,仍可以调用派生类的成员函数!
二、虚函数的使用方法
在基类中使用virtual声明成员函数为虚函数:
类内声明: virtual [类型] 函数名([参数表列])
在类外定义虚函数时,不再加virtual;
使用虚函数,可获得如下效果:
1.派生类根据需要可以重新定义函数体(函数覆盖),使用虚函数提高了程序的可扩充性;
2.成员函数被声明为虚函数后,其派生类中函数覆盖的同名函数自动成为虚函数;
3.若虚函数在派生类中未重定义,则派生类简单地继承其直接基类的虚函数;
4.指向基类的指针,当指向派生类对象时,则可以调用派生类的方法。
再举一个例子:
#include <iostream>
#include <string>
using namespace std;class Circle
{
public:Circle(double r=0):radius(r) { }double area ( ) const//virtual double area ( ) const//定义为虚函数!!!{return 3.14159*radius*radius;//圆形面积公式s = πr²}
protected:double radius;
};class Cylinder:public Circle
{
public:Cylinder (double r=0,double h=0):Circle(r),height(h) {}double area() const{return 2*Circle::area( )+2*3.14159*Circle::radius*height;//圆柱面积公式:2个底面积+1个侧面积,其中侧面积 = 2πrh};
protected:double height;
};int main( )
{Circle c1(5.2);Cylinder cy1(5.2, 10);Circle *pc=&c1;cout<<pc->area()<<endl;pc = &cy1;cout<<pc->area()<<endl;return 0;
}
由于只定义了一个指向基类Circle对象的指针pc(未定义指向派生类对象的指针),因此
基类成员函数“double area ( ) const”不定义为虚函数时的运算结果:
定义为虚函数时的运算结果:
三、静态关联和动态关联
通过关联,把一个标识符和一个存储地址联系起来;即把一个函数名与一个类对象捆绑在一起。
静态关联:函数重载和通过对象名调用的(虚)函数,在编译阶段即可确定其调用的(虚)函数属于哪一个类;
动态关联:通过基类指针与虚函数,在代码运行阶段才确定关联关系;动态关联提供动态的多态性,即运行阶段的多态性。
四、何时使用虚函数
(一)何时使用
首先,如果一个类要作为基类,其成员函数可以定义为虚函数;如果不是基类,省点事吧!
其次,看成员函数在类被继承后有无可能被更改功能。如果希望更改其功能的,一般应该在基类中将其声明为虚函数,如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不声明为虚函数。
再次,应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问。如果是通过基类指针或引用去访问的,则应当声明为虚函数。
有时,在定义虚函数时,并不定义其函数体,即函数体是空的,只等着被继承。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加——纯虚函数。
(二)关于虚函数的进一步理解
虚函数只能是类的成员函数,而不能将类外的普通函数声明为虚函数。
虚函数的作用是允许在派生类中对基类的虚函数重新定义(函数覆盖),只能用于类的继承层次结构中。
排他性:一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义非virtual,但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。
使用虚函数系统要有少量的空间开销:当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(一个指针数组),用于存放每个虚函数的入口地址。
五、虚析构函数
(一)虚析构函数举例
当派生类的对象从内存中撤销时,一般先调用派生类的析构函数,然后再调用基类的析构函数。问题由此而来:
#include <iostream>
#include <string>
using namespace std;class Point{
public:Point() {}~Point()//virtual ~Point()// 定义基类的析构函数为虚函数时{cout<<"executing ~Point destructor OK"<<endl;}
};class Circle:public Point
{
public:Circle(){}~Circle( ){cout<<"executing ~Circle destructor OK"<<endl;}
};int main( )
{Point *p=new Circle;delete p;return 0;
}
运行结果:
发现只执行了基类Point的析构函数,派生类Circle的析构函数未执行!
即:用new运算符建立了派生类对象,并且由一个基类的指针变量指向该派生类对象,用delete运算符撤销对象时,系统只执行基类的析构函数,而不执行派生类的析构函数——派生类对象析构中要求的工作将被忽略。
而如果基类的析构函数定义为虚函数时,即“virtual ~Point()”,执行结果如下:
由此可见,如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。
(二)虚析构函数用法
当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统会采用动态关联,自动调用相应的析构函数,对该对象进行清理工作:先调用了派生类的析构函数,再调用了基类的析构函数,符合人们的愿望;
最好把基类的析构函数声明为虚函数,这将使所有派生类的析构函数自动成为虚函数;
虚析构函数是很重要的技巧:专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证撤销动态分配空间时能正确的处理。
构造函数不能声明为虚函数。
六、纯虚函数
虚函数,有时并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名。
在设计类的层次结构过程中,常常有的基类的成员函数并无意义,具体功能由派生类决定。
纯虚函数是只有函数的名字而不具备函数的功能,不能被调用的虚成员函数。
(一)纯虚函数的定义
纯虚函数的定义格式:
virtual 函数类型 函数名 (参数表列) =0
virtual float area()=0; //纯虚函数
virtual float area() const =0; //纯虚常函数//注意区别
virtual float area() const {return 0}; //虚函数
①纯虚函数没有函数体;
②最后面的“=0” 只起形式上的作用,告诉编译系统“这是纯虚函数”;
③这是一个声明语句,最后应有分号。
如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍
然为纯虚函数。
(二)纯虚函数的应用
#include <iostream>
#include <string>
using namespace std;class Animal
{
public:virtual void cry() = 0; //定义基类中的纯虚函数
};class Mouse : public Animal
{
public:void cry(){cout<<"zhi zhi zhi"<<endl;}
};class Dog : public Animal
{
public:virtual void cry(){cout<<"wang wang wang"<<endl;}
};class Cat : public Animal
{
public:virtual void cry(){cout<<"miao miao miao"<<endl;}
};int main( )
{Animal *p; //定义p为指向基类Animal的指针//p = new Animal();//p->cry();Mouse m1;p=&m1;p->cry();Cat c1;p=&c1;p->cry();Dog d1;p=&d1;p->cry();return 0;
}
运行结果:
由上可知:
纯虚函数功能的实现,只能在派生类中
有纯虚函数的类,丧失了定义对象的能力
有纯虚函数的类,是专门来当基类的!