自:http://zhangjunhd.blog.51cto.com/113473/57543
1. 虚函数
1.1 虚函数的作用
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
class Time{
public :
Time(int =0,int =0,int =0);
void show();
protected :
int hour;
int min;
int sec;
};
class LocalTime:public Time{
public :
LocalTime(int =0,int =0,int =0,string="+8" );
void show();
protected :
string zone;
};
Time::Time(int h,int m,int s):hour(h),min(m),sec(s){}
void Time::show(){
cout<<hour<<":" <<min<<":" <<sec<<endl;
}
LocalTime::LocalTime(int h,int m,int s,string z):Time(h,m,s),zone(z){}
void LocalTime::show(){
cout<<hour<<":" <<min<<":" <<sec<<"@" <<zone<<endl;
}
int main(){
Time t;
LocalTime lt;
Time *pt=&t;
pt->show();
pt=<
pt->show();
system("PAUSE" );
return EXIT_SUCCESS;
}
|
结果:
0:0:0
0:0:0
这里通过指针找到派生类,但无法调用派生类 show() 。如果使用虚函数。
将基类 Time 中的 show() 函数声明为虚函数, 其余不变。
class Time{
public :
Time(int =0,int =0,int =0);
virtual void show();
…
};
|
结果:
0:0:0
0:0:0@+8
本来,基类指针是指向基类对象的,如果用它指向派生类对象,先进行指针类型转换 ,将派生类对象的指针先转换为基类指针 ,所以基类指针指向的是派生类对象中的基类部分。在程序修改前,是无法通过基类指针去调用派生类对象中的成员函数的。
虚函数突破这一限制,在派生类的基类部分中,派生类的虚函数 覆盖了 基类原来的虚函数 ,因此在使用基类指针指向派生类对象后,调用虚函数时就调用了派生类的虚函数。
1.2 虚函数的使用方法
【 1 】在基类用 virtual 声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。
【 2 】在派生类中重新定义此函数,要求函数名、函数(返回)类型、函数参数个数和类型 与基函数的虚函数相同。如果在派生类中没有对基类的虚函数重定义,则派生类简单地继承直接基类的虚函数。
!!!有一种情况例外 ,在这种情况下派生类与基类的成员函数返回类型不同,但仍起到虚函数的作用。即基类虚函数返回一个基类指针 或基类引用 ,而子类的虚函数返回一个子类的指针 或子类的引用 。
class Base{
public :
virtual Base *fun(){
cout<<"Base's fun()." <<endl;
return this ;
}
};
class Derived:public Base{
public :
virtual Derived *fun(){
cout<<"Derived's fun()." <<endl;
return this ;
}
};
void test(Base &x){
Base *b;
b=x.fun();
}
int main(){
Base b;
Derived d;
test(b);
test(d);
system("PAUSE" );
return EXIT_SUCCESS;
}
|
结果:
Base's fun().
Derived's fun().
【 3 】 C++ 规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数(符合 2 中定义的函数)都自动成为虚函数,不管是否冠以virtual关键字。也就是说只要基类声明为virtual,其后一直为virtual。
【 4 】定义一个指向基类对象的指针变量,并使其指向同一类族(可能为子类)中的某个对象。通过该指针变量调用此函数,此时调用的就是指针变量指向的对象的同名函数。
1.3 声明虚函数的限制
【 1 】只能用 virtual 声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。
【 2 】一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非 virtual 的但与该虚函数具有相同参数(个数与类型)和函数返回值类型的同名函数。
【 3 】静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
【 4 】 inline 函数不能是虚函数,因为 inline 函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时,仍将其视为非 inline 的。
【 5 】使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译器会为该类构造一个虚函数表 (virtual function tanle,vtable) ,它是一个指针数组,存放每个虚函数的入口地址。
【 6 】构造函数不能为虚函数,析构函数可以。
2. 虚析构函数
class Time{
public :
Time(int =0,int =0,int =0);
~Time(){
cout<<"Time destructor" <<endl;
}
protected :
int hour;
int min;
int sec;
};
class LocalTime:public Time{
public :
LocalTime(int =0,int =0,int =0,string="+8" );
~LocalTime(){
cout<<"LocalTime destructor" <<endl;
}
protected :
string zone;
};
Time::Time(int h,int m,int s):hour(h),min(m),sec(s){}
LocalTime::LocalTime(int h,int m,int s,string z):Time(h,m,s),zone(z){}
int main(){
Time *p=new LocalTime;// 指向派生类
delete p;
system("PAUSE" );
return EXIT_SUCCESS;
}
|
结果:
Time destructor
从结果可以看出,执行的还是基类的析构函数,而程序的本意是希望执行派生类的析构函数。此时将基类的析构函数声明为虚析构函数,
virtual ~Time(){
cout<<"Time destructor" <<endl;
}
|
结果:
LocalTime destructor
Time destructor
如果将基类的析构函数声明为虚函数,由该基类所派生的所有派生类的析构函数也自动成为虚函数。
把基类的析构函数声明为虚函数的好处是,如果程序中 delete 一个对象,而 delete 运算符的操作对象是指向派生类对象的基类指针 ,则系统会调用相应类的析构函数(派生类的析构函数)。
构造函数不能声明为虚函数。
3. 纯虚函数
virtual void show()=0;// 纯虚函数
|
这里将 show() 声明为纯虚函数 (pure virtual function) 。纯虚函数是在声明虚函数时被“初始化”为 0 的虚函数。
声明纯虚函数的一般形式为,
virtual 函数类型 函数名 ( 参数列表 )=0;
|
纯虚函数没有函数体;最后的“ =0” 并不代表函数返回值为 0 ,它只起形式上的作用,告诉编译器“这是纯虚函数”;这个一个声明语句,最后有分号。
声明纯虚函数是告诉编译器,“在这里声明了一个虚函数,留待派生类中定义”。在派生类中对此函数提供了定义后,它才能具备函数的功能,可以被调用。
纯虚函数的作用是在基类中为其派生类保留了一个函数的名字,以便派生类根据需要对它进行定义。
如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该函数在派生类中仍为纯虚函数。
4. 抽象类
将不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类 (abstract class) ,由于它常用作基类,通常称为抽象基类。凡是包含纯虚函数的类都是抽象类。
如果在派生类中没有对所有的纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。
可以定义指向抽象类数据的指针变量。当派生类成为具体类后,就可以用这个指针指向派生类对象,然后通过该指针调用虚函数。