Before getting any deeper into this chapter, you should have a proper understanding of pointers and class inheritance. If you are not really sure of the meaning of any of the following expressions, you should review the indicated sections:
Pointers to base class
One of the key features of class inheritance is that a pointer to a derived class is type-compatible with a pointer to its base class. Polymorphism is the art of taking advantage of this simple but powerful and versatile feature. The example about the rectangle and triangle classes can be rewritten using pointers taking this feature into account:
// pointers to base class
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
};

class Rectangle: public Polygon {
public:
int area()
{ return width*height; }
};

class Triangle: public Polygon {
public:
int area()
{ return width*height/2; }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << rect.area() << '\n';//20
cout << trgl.area() << '\n';//10
return 0;

Function main declares two pointers to Polygon (named ppoly1 and ppoly2). These are assigned the addresses of rect and trgl, respectively, which are objects of type Rectangle and Triangle. Such assignments are valid, since both Rectangle and Triangle are classes derived from Polygon. Dereferencing ppoly1 and ppoly2 (with *ppoly1 and *ppoly2) is valid and allows us to access the members of their pointed objects. For example, the following two statements would be equivalent in the previous example:
ppoly1->set_values (4,5);
rect.set_values (4,5);
But because the type of ppoly1 and ppoly2 is pointer to Polygon (and not pointer to Rectangle nor pointer to Triangle), only the members inherited from Polygon can be accessed, and not those of the derived classes Rectangle and Triangle. That is why the program above accesses the area members of both objects using rect and trgl directly, instead of the pointers; the pointers to the base class cannot access the area members. Member area could have been accessed with the pointers to Polygon if area were a member of Polygon instead of a member of its derived classes, but the problem is that Rectangle and Triangle implement different versions of area, therefore there is not a single common version that could be implemented in the base class.
Virtual members
One of the key features of class inheritance is that a pointer to a derived class is type-compatible with a pointer to its base class. Polymorphism is the art of taking advantage of this simple but powerful and versatile feature. The example about the rectangle and triangle classes can be rewritten using pointers taking this feature into account:
// virtual members
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area ()
{ return 0; }
};

class Rectangle: public Polygon {
public:
int area ()
{ return width * height; }
};

class Triangle: public Polygon {
public:
int area ()
{ return (width * height / 2); }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon poly;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
Polygon * ppoly3 = &poly;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly3->set_values (4,5);
cout << ppoly1->area() << '\n';//20
cout << ppoly2->area() << '\n';//10
cout << ppoly3->area() << '\n';//0
return 0;
}
In this example, all three classes (Polygon, Rectangle and Triangle) have the same members: width, height, and functions set_values and area. The member function area has been declared as virtual in the base class because it is later redefined in each of the derived classes. Non-virtual members can also be redefined in derived classes, but non-virtual members of derived classes cannot be accessed through a reference of the base class: i.e., if virtual is removed from the declaration of area in the example above, all three calls to area would return zero, because in all cases, the version of the base class would have been called instead. Therefore, essentially, what the virtual keyword does is to allow a member of a derived class with the same name as one in the base class to be appropriately called from a pointer, and more precisely when the type of the pointer is a pointer to the base class that is pointing to an object of the derived class, as in the above example. A class that declares or inherits a virtual function is called a polymorphic class. Note that despite of the virtuality of one of its members, Polygon was a regular class, of which even an object was instantiated (poly), with its own definition of member area that always returns 0. 在成员函数的形参后面写上=0,则成员函数为纯虚函数。 纯虚函数声明: virtual 函数类型 函数名 (参数表列) = 0;
class Person
{
virtual void Display () = 0; // 纯虚函数
protected :
string _name ; // 姓名
};
class Student : public Person
{};
注意: (1)纯虚函数没有函数体; (2)最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是虚函数”; (3)这是一个声明语句,最后有分号。 纯虚函数只有函数的名字而不具备函数的功能,不能被调用。 纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对他进行定义。如果在基类中没有保留函数名字,则无法实现多态性。 如果在一个类中声明了纯虚函数,在其派生类中没有对其函数进行定义,则该虚函数在派生类中仍然为纯虚函数。
抽象类: 不用定义对象而只作为一种基本类型用作继承的类叫做抽象类(也叫接口类),凡是包含纯虚函数的类都是抽象类,抽象类的作用是作为一个类族的共同基类,为一个类族提供公共接口,抽象类不能实例化出对象。 纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。 总结: 1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外) 2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。 3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。 4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。 5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆 6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。 7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理) 8、虚表是所有类对象实例共用的虚表剖析。 虚函数和抽象基类的应用:
#include <iostream>
using namespace std;

//声明抽象基类Shape
class Shape
{
public:
virtual float area()const //虚函数
{
return 0.0;
}


virtual void shapeName()const  = 0;//纯虚函数
//shapeNamea函数的作用是输出具体的形状,在派生类中定义,因此声明为纯虚函数
};

//声明Point类
class Point:public Shape
{
public:
Point(float a = 0.0, float b = 0.0)
{
x = a;
y = b;
}

void setPoint(float a, float b)
{
x = a;
y = b;
}

float getX()const
{
return x;
}

float getY()const
{
return y;
}

virtual void shapeName()const
{
cout<<"Point:";
}

friend ostream & operator <<(ostream &_cout, const Point &p)
{
_cout<<"["<<p.x<<","<<p.y<<"]";
return _cout;
}

protected:
float x;
float y;
};

//声明Ciircle类
class Circle:public Point
{
public:
Circle(float a = 0.0, float b = 0.0, float r = 0.0)
:Point(a, b)
,radius(r)
{}

void setRadius(float r)

{
radius = r;
}

float getRadius()const
{
return radius;
}

virtual float area()const
{
return 3.1415926*radius*radius;
}

virtual void shapeName()const
{
cout<<"Circle:";
}

friend ostream & operator <<(ostream &_cout, const Circle &c)
{
_cout<<"["<<c.x<<","<<c.y<<"],r="<<c.radius;
return _cout;
}

protected:
float radius;
};

int main()
{
Point point(3.2, 4.5);  // 建立Point类对象point
Circle circle(2.4, 1.2, 5.6);  //建立Circle类对象circle

point.shapeName();        //静态关联
cout<<point<<endl;

circle.shapeName();       //静态关联
cout<<circle<<endl;

Shape* pt;    //定义基类指针
pt = &point;
pt->shapeName();
cout<<"x="<<point.getX()<<",y="<<point.getY()<<endl;
cout<<"area="<<pt->area()<<endl;

pt = &circle;    //指针指向Circle类对象
pt->shapeName();   //动态关联
cout<<"x="<<circle.getX()<<",y="<<circle.getY()<<endl;
cout<<"area="<<pt->area()<<endl;

system("pause");
return 0;
}
运行结果: 结论: (1)一个基类如果包含一个或一个以上纯虚函数,就是抽象基类。抽象基类不能也没必要定义对象。 (2)在类的层次结构中,顶层或最上面几层可以是抽象基类。抽象基类体现了本类族中各类的共性,把各类中共有的成员函数集中在抽象基类中声明。 (3)抽象基类是本类族的共公共接口,即就是从同一基类中派生出的多个类有同一接口。