开始之前,我们首先假设这样一组形状类。Shape,Point,Circle很明显,它们有着Shape<-Point<-Circle的继承关系,每个类都有一个printName,print,area函数,用来打印自己的类型、相关数据以及面积。当我们想要得到不同形状的对象的这些信息时,我们自然希望能统一的作为Shape的对象来处理,这就要用到虚函数。
1、虚函数
简单的说,虚函数就是在函数原型前加上virtual关键字。函数一旦被声明为虚函数,即使类在改写它的时候没有将其声明为虚函数,它从该点的继承层次结构中仍然是虚函数。如果基类中一个函数被声明为虚函数,在若干子类中有着不同的实现,那么我们在创建若干子类的不同对象时,可以用基类的指针或者引用来指明子类对象并且调用相应子类的函数。
Virtual
void
print();
2、纯虚函数
纯虚函数是在声明虚函数时初始化为0的函数。
Virtual
void print()
const
=
0
;
3、抽象类
简单的说,带有一个或多个未实现的纯虚函数的类是抽象类。如果一个类继承自一个抽象类,但它没有全部实现父类里面所有的纯虚函数,那么那些纯虚函数在子类中仍然是纯虚的,这个子类仍然是抽象类,仍然不能实例化对象。
虽然不能实例化抽象类,但我们可以声明一个抽象类的指针和引用,在实例化对象时,可以用不同的子类来实现,从而实现多态性操作。
4、多态性
简单的说,多态性就是对象能够对同一个函数调用作出不同的响应。多态性通过虚函数来实现,当我们用基类的指针来调用虚函数时,程序会根据对象的属性选择其自身的函数实现。即使程序员不知道对象类型,程序仍然可以作出适合该对象类型的行为。
5、虚析构函数
用多态性动态分配对象会产生一个问题,就是当我们delete一个指向对象的基类指针时,基类的析构函数仍然会被调用。解决的方法是把基类的析构函数声明为虚的,这样可以利用虚函数的性质来调用适合该对象的合适的类的析构函数。当对象被删除时,派生类的基类部分也会被删除——在派生类析构函数之后自动执行基类的析构函数。所以,如果一个类有虚函数,即使该类不需要虚析构函数,也最好提供一个虚析构函数,以保证该类派生出来的子类所包括的析构函数能被正确调用。下面来看一个综合的例子
//
shape.h
#ifndef SHAPE
#define
SHAPE


class
Shape
...
{
public:

Virtual double area() const ...{ return 0.0; }
Virtual void printName() const =0;//纯虚函数
Virtual void print() const = 0;//纯虚函数
}
;
#endif

/**/
/*****************************************************************/
//
pint.h
#ifndef POINT
#define
POINT
#include
<
iostream
>
using
std::cout;
#include
"
shape.h
"

class
Point:
public
Shape
...
{
public:
Pint(int = 0, int = 0);
void setPoint(int, int);

int getX() const ...{ return x; }

int getY const ...{ return y; }

Virtual void printName() const ...{ cout << "Point: ";}
Virtual void print() const;
private:
int x, y;
}
;
#endif

/**/
/********************************/
//
point.cpp
#include
"
point.h
"
void
Point::setPoint(
int
a,
int
b)

...
{
x = a;
y = b;
}
void
Point::print()

...
{
cout << '[ '<<x<<" , "<<y<<' ]';
}

/**/
/**************************************************************************/
//
circle.h
#ifndef CIRCLE
#define
CIRCLE
#include
"
point.h
"

class
Circle:
public
Point
...
{
public:
Circle(double r = 0.0,int x =0,int y = 0);
void setRadius( double );
double getRadius() const;
Virtual double area() const;

Virtual void printName() const ...{ cout << "Circle:";}
virtual void print() const;
private:
double radius;
}
;
#endif

/**/
/******************************/
//
circle.cpp
#include
<
iostram
>
using
std::cout;
#include
"
circle.h
"
Circle::Circle(
double
r,
int
a,
int
b) : Point( a, b)
//
调用基类构造函数

...
{
setRadius( r );
}
void
Circle::setRadius(
double
r )

...
{
radius = r > 0 ? r : 0;
}
double
Circle::getRadius()
const

...
{
return radius;
}
double
Circle::area()
const
//
实现area

...
{
return 3.14159 * radius * radius;
}
void
Circle::print()
const

...
{
Point:print();
cout << "; Radius = " << radius;
}

/**/
/***************************************************************/
//
test.cpp
#include
<
iostream
>
using
std::cout;
using
std::endl;
#include
<
iomanip
>
using
std::ios;
using
std::setiosflags;
using
std::setprecision;
#include
"
shape.h
"
#include
"
point.h
"
#include
"
circle.h
"
void
usePointer(
const
Shape
*
);
void
useReference (
const
Shape
&
);
int
main()

...
{
cout << setiosflags ( ios::showpoint) << setprecision( 2 );
Point point( 7, 11);
Circle circle(3.5, 22, 8);
//显示调用各对象自己的函数
point.printName();
point.print();
cout << endl;
circle.printName();
circle.print();
cout << endl;
Shape * shapes[2];

shapes[0] = &point;
shapes[1] = &circle;
cout << endl <<"Using base-class point"<<endl;
int i;
for( i = 0; i < 2; i++)
usePointer(shapes[i]);//指针调用

cout << endl <<"Using base-class references"<<endl;
for(i = 0;i < 2; i++)
useReference(*shapes[i]);//引用调用
return 0;
}
void
usePointer(
const
Shape
*
shepe_ptr)
//
指针调用实现多态

...
{
shape_ptr -> printName();
shape_ptr -> print();
cout << endl << shape_ptr -> area()<<endl;
}
void
useReference(
const
Shape
&
shape_Ref)
//
引用调用实现多态

...
{
shape_Ref.printName();
shape_Ref.print();
cout << endl << shape_Ref.area()<<endl;
}
6、多态性、虚函数、动态绑定的本质
通过上面的例子,我们应该对动态绑定的概念以及虚函数的应用有了基本掌握,但是它们到底是怎么实现的,仍然是一个问题,下面就来介绍一下:
当编译一个或多个虚函数的类时,C++会为这些类建立一个虚函数表(vtable)。每次执行该类的虚函数时,程序都会用vtable来选择恰当的实现方法,如图(图中的实心黑色圆点表示指针):
假如我们调用shapes[1]的printName方法,那么实现过程如上图加粗线表示:
1
,首先我们得到了shape类型的指针
2
,通过复引用指针得到circle对象(我们此时仍然不知道对象类型)
3
,复引用circle对象的vtable指针以得到circle vtable
4
,跳过4字节得到printName函数的指针
5
,复引用printName函数的指针来执行相应操作
至此,我们对虚函数的实现有了相应的认识,但要注意的一点是,动态绑定是以多次指针复引用为代价的,因此在性能要求很高的情况下要考虑这里面的运行时间。
如果还没理解的话,我们可以这样来想,为什么可以动态绑定函数呢?就是因为我们在对象里保存了它所能调用的虚函数的信息,不同类型的对象所包含的虚函数信息是不同的,因此我们可以“动态”的绑定函数。
——总结自C++编程金典