1.虚函数:
用virtual定义的非static实例成员函数,虚函数可表现多态性,非虚函数的执行效率比较高,但是不能表现多态性; .虚函数总是有this,故参数后面可出现const 和 volatile,而staic成员函数无 this,故参数表后不可出现const 和 volatile,所以virtual 和 static 不能同时出现,否则关于this是矛盾的; 动态多态:重载函数表现的是静态(编译时)多态性,虚函数表现的是动态(运行时)的多态,多态一般指动态的编译; 重载函数是静态多态函数 ,通过早期绑定调用重载函数,虚函数是动态多态函数,通过晚期绑定调用函数;晚期绑定 是程序运行时由程序自己完成的,早期绑定 是编译或者操作系统完成的; 虚函数的晚期绑定通过存储在对象中的一个指向虚函数入口地址表 VFT的指针完成; 虚函数一般 在基类的public或protected部分,在派生类中定义取代型函数时,函数原型必须和基类的虚函数必须完全相同;无论是否使用virtual保留字都将成为虚函数; 虚函数只有 在具有继承关系的类称重才需要表现多态,
union
既不能作为基类,也不能作为派生类,故union
不能定义为虚函数;构造对象 时类型是确定的,不需要根据类型不同表现为不同多态行为,故构造函数 不能定义为虚函数;析构函数 可通过父类指针,引用或delete调用,父类指针可能指向父类或子类对象,因此析构函数需要多态性; 虚函数可声明或者自动称为inline函数,也可重载,缺省和省略参数; (虚函数一定是内联失败的:根据地址表 取地址晚期绑定,内联失败); 虚函数不能使用constexper 定义,故构造函数 可用 constexper定义,但是有虚基类的派生类构造不能使用constexper定义;
示例代码
#include <iostream>
using namespace std;
class POINT
{
int x, y;
public :
int getx ( ) { return x; }
int gety ( ) { return y; }
virtual void show ( ) { cout << "Show a point\n" ; }
POINT ( int x, int y) : x ( x) , y ( y) { }
} ;
class CIRCLE : public POINT
{
int r;
void show ( ) { cout << "Show a circle\n" ; }
public :
int getr ( ) { return r; }
CIRCLE ( int x, int y, int r) : POINT ( x, y) { CIRCLE:: r = r; }
} c ( 3 , 7 , 8 ) ;
int main ( )
{
POINT * p = & c;
cout << "r: " << c. getr ( ) << " ,x: " << p- > getx ( ) << endl;
p- > show ( ) ;
system ( "pause" ) ;
return 0 ;
}
输出结果
r: 8 , x: 3
Show a circle
2. 虚析构函数
如果基类的析构函数定义为虚函数,则派生类的析构函数就会自动称为析构函数; 在使用delete运算符销毁一个对象时,多态特性保证执行的析构函数就是该对象的自己的析构函数,故最好将可能被继承的类的析构函数都定义为虚析构函数; 如果为基类和派生类对象的成员分配了动态内存,则 一定要将基类和派生类的析构函数定义为虚析构函数,否则,派生类对象可能错误调用基类析构函数,造成内存泄漏并导致出现内存(一般性)保护错误。
示例代码
#include <iostream>
using namespace std;
class STACK
{
int * e, p, c;
public :
virtual int getp ( ) { return p; }
virtual int push ( int f) { return p < c ? ( e[ p++ ] , 1 ) : 0 ; }
virtual int pop ( int & f) { return p > 0 ? ( f = e[ -- p] , 1 ) : 0 ; }
STACK ( int m) : e ( new int [ m] ) , c ( e ? m : 0 ) , p ( 0 ) { }
virtual ~ STACK ( )
{
if ( e)
delete e;
e = 0 ;
c = 0 ;
p = 0 ;
}
} ;
class QUEUE : public STACK
{
STACK s;
public :
virtual int enter ( int f)
{
return s. getp ? push ( f) : s. push ( f) ;
}
virtual int leave ( int & f)
{
if ( ! s. getp ( ) )
while ( pop ( f) )
s. push ( f) ;
return s. pop ( f) ;
}
QUEUE ( int m) : STACK ( m) , s ( m) { }
~ QUEUE ( ) { }
} ;
int main ( )
{
STACK * p = new QUEUE ( 9 ) ;
delete p;
return 0 ;
}
说明
如果~STACK没有定义为虚函数,则delete p调用析构函数~STACK,把QUEUE(9)当做基类的STACK对象析构,只释放基类对象成员e占用的内存;未析构对象成员s(不释放s.e)造成内存泄露; 如果~STACK定义为虚函数,则delete p调用析构函数~QUEUE,把QUEUE(9)当做QUEUE对象析构; 父类有址引用变量引用子类对象时不须析构。若引 用new产生的子类对象,则必须用delete &析构:
STACK & z= * new QUEUE ( 20 ) ;
delete & z;
上述delete &z完成了两个任务:①调用析构函数 ~QUEUE( ),释放其基类和对象成员各自为整型指 针e分配的空间;②释放QUEUE对象自身占用的存 储空间。 如将delete &z改为z.~STACK( ),则只完成任务①而 没完成②;如果改为free(&z),则只完成任务②而没 完成①,都会造成内存泄露。为什么z.~STACK( ) 执行~QUEUE( )?(析构多态以及z实现为指针)。
class A
{
public :
virtual void foo ( )
{
cout<< "A::foo() is called" << endl;
}
} ;
class B : public A
{
public :
void foo ( )
{
cout<< "B::foo() is called" << endl;
}
} ;
int main ( void )
{
A * a = new B ( ) ;
a- > foo ( ) ;
return 0 ;
}
虚就虚在所谓"推迟联编"或者"动态联编"上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为"虚"函数。虚函数只能借助于指针或者引用来达到多态的效果。