自: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()
。如果使用虚函数。
show()
。如果使用虚函数。
将基类
Time
中的
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
声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。
1
】在基类用
virtual
声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。
【
2
】在派生类中重新定义此函数,要求函数名、函数(返回)类型、函数参数个数和类型
与基函数的虚函数相同。如果在派生类中没有对基类的虚函数重定义,则派生类简单地继承直接基类的虚函数。
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。
3
】
C++
规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数(符合
2
中定义的函数)都自动成为虚函数,不管是否冠以virtual关键字。也就是说只要基类声明为virtual,其后一直为virtual。
【
4
】定义一个指向基类对象的指针变量,并使其指向同一类族(可能为子类)中的某个对象。通过该指针变量调用此函数,此时调用的就是指针变量指向的对象的同名函数。
4
】定义一个指向基类对象的指针变量,并使其指向同一类族(可能为子类)中的某个对象。通过该指针变量调用此函数,此时调用的就是指针变量指向的对象的同名函数。
1.3
声明虚函数的限制
声明虚函数的限制
【
1
】只能用
virtual
声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。
1
】只能用
virtual
声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。
【
2
】一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非
virtual
的但与该虚函数具有相同参数(个数与类型)和函数返回值类型的同名函数。
2
】一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非
virtual
的但与该虚函数具有相同参数(个数与类型)和函数返回值类型的同名函数。
【
3
】静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
3
】静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
【
4
】
inline
函数不能是虚函数,因为
inline
函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时,仍将其视为非
inline
的。
4
】
inline
函数不能是虚函数,因为
inline
函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时,仍将其视为非
inline
的。
【
5
】使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译器会为该类构造一个虚函数表
(virtual function tanle,vtable)
,它是一个指针数组,存放每个虚函数的入口地址。
5
】使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译器会为该类构造一个虚函数表
(virtual function tanle,vtable)
,它是一个指针数组,存放每个虚函数的入口地址。
【
6
】构造函数不能为虚函数,析构函数可以。
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
运算符的操作对象是指向派生类对象的基类指针
,则系统会调用相应类的析构函数(派生类的析构函数)。
delete
一个对象,而
delete
运算符的操作对象是指向派生类对象的基类指针
,则系统会调用相应类的析构函数(派生类的析构函数)。
构造函数不能声明为虚函数。
3.
纯虚函数
纯虚函数
virtual
void show()=0;// 纯虚函数 |
这里将
show()
声明为纯虚函数
(pure virtual function)
。纯虚函数是在声明虚函数时被“初始化”为
0
的虚函数。
show()
声明为纯虚函数
(pure virtual function)
。纯虚函数是在声明虚函数时被“初始化”为
0
的虚函数。
声明纯虚函数的一般形式为,
virtual
函数类型 函数名 ( 参数列表 )=0; |
纯虚函数没有函数体;最后的“
=0”
并不代表函数返回值为
0
,它只起形式上的作用,告诉编译器“这是纯虚函数”;这个一个声明语句,最后有分号。
=0”
并不代表函数返回值为
0
,它只起形式上的作用,告诉编译器“这是纯虚函数”;这个一个声明语句,最后有分号。
声明纯虚函数是告诉编译器,“在这里声明了一个虚函数,留待派生类中定义”。在派生类中对此函数提供了定义后,它才能具备函数的功能,可以被调用。
纯虚函数的作用是在基类中为其派生类保留了一个函数的名字,以便派生类根据需要对它进行定义。
如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该函数在派生类中仍为纯虚函数。
4.
抽象类
抽象类
将不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类
(abstract
class)
,由于它常用作基类,通常称为抽象基类。凡是包含纯虚函数的类都是抽象类。
(abstract
class)
,由于它常用作基类,通常称为抽象基类。凡是包含纯虚函数的类都是抽象类。
如果在派生类中没有对所有的纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。
可以定义指向抽象类数据的指针变量。当派生类成为具体类后,就可以用这个指针指向派生类对象,然后通过该指针调用虚函数。