参考书目:《C++面向对象程序设计》—— 谭浩强 《C++程序设计:思想与方法》—— 翁惠玉
在C++ 程序设计中,多态性是指具有不同功能的函数可以用一个函数名,在面向对象方法中这样描述多态性:向不同的对象发送同一个消息,它们接收后会产生不同的行为(即方法)。函数的重载、运算符重载都是多态现象。具有不同功能的函数可以用同一个函数名,即用一个函数名调用不同内容的函数。
从系统实现的观点看,多态性分为两类:静态多态和动态多态性。以前学过的函数重载和运算符重载属于静态多态性,在编译程序时系统就可以确定调用哪个函数,因此静态多态性又称编译时的多态性。静态多态性是通过函数重载实现的。动态多态性是在程序运行中才能确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚函数实现的。
例:建立一个点类(point),有数据成员x,y(坐标)。以它为基类派生一个圆类,增加数据成员r(半径),再以圆为直接基类派生出一个圆柱体类,再增加数据成员h(高)要求重载运算符<<和>>使之能输出以上的类对象。
(1)声明基类point类
class Point
{
protected:
float x, y;
public:
Point(float = 0, float = 0);
void setPoint(float, float);
float getX() const { return x; }
float getY() const { return y; }
friend ostream & operator<<(ostream &, const Point &);
};
Point::Point(float a, float b) { x = a; y = b; }
void Point::setPoint(float a, float b) { x = a; y = b; }
ostream & operator<<(ostream &output, const Point &p)
{
output << "[" << p.x << "," << p.y << "]" << endl;
return output;
}
void main()
{
Point p(3.5, 6.4);
cout << "x=" << p.getX() <<",y=" << p.getY() << endl;
p.setPoint(8.5, 6.8);
cout << "p(new):" << p << endl;
}
声明派生类circle类
class Circle :public Point
{
protected:
float radius;
public:
Circle(float x = 0, float y = 0, float r = 0);
void setRadius(float r) { radius = r; }
float getRadius() const { return radius; }
float area() const;
friend ostream &operator<<(ostream &, const Circle &);
};
Circle::Circle(float a, float b, float r) :Point(a, b), radius(r) {}
float Circle::area() const { return 3.14159*radius*radius; }
ostream &operator<<(ostream &output, const Circle &c)
{
output << "Center=[" << c.x << "," << c.y << "], Radius=" << c.radius << ", area=" << c.area() << endl;
return output;
}
void main()
{
Circle c(3.5, 6.4, 5.2);
cout << "original circle:\nx="<< c.getX() << ", y="<< c.getY() << ", r=" << c.getRadius() <<", area="<<c.area()<<endl;
c.setRadius(7.5);
c.setPoint(5, 5);
cout << "new circle:\n" << c;
Point &pRef = c;
cout << "pRef:" << pRef;
}
(3)声明circle 的派生类cylinder类
class Cylinder :public Circle
{
public:
Cylinder(float x = 0, float y = 0, float r = 0, float h = 0);
void setHeight(float h) { height = h; }
float getHeight() const { return height; }
float area() const;
float volume() const;
friend ostream& operator<<(ostream&, const Cylinder&);
protected:
float height;
};
Cylinder::Cylinder(float a, float b, float r, float h) :Circle(a, b, r) { height = h; }
float Cylinder::area() const
{
return 2 * Circle::area() + 2 * 3.14159*radius*height;
}
float Cylinder::volume() const { return Circle::area()*height; }
ostream &operator<<(ostream &output, const Cylinder& cy)
{
output << "Center=[" << cy.x << "," << cy.y << "], r=" << cy.radius<< ", h=" << cy.height << " \narea=" << cy.area() <<", volume=" << cy.volume() << endl;
return output;
}
int main()
{
Cylinder cy1(3.5, 6.4, 5.2, 10);
cout << "\n original cylinder:\n x=" << cy1.getX() << ", y="
<< cy1.getY() << ", r=" << cy1.getRadius() << ", h="
<< cy1.getHeight() << "\narea=" << cy1.area()
<< ", volume="<<cy1.volume()<<endl;
cy1.setHeight(15);
cy1.setRadius(7.5);
cy1.setPoint(5, 5);
cout << "\nnew cylinder:\n" << cy1;
Point &pRef = cy1;
cout << "\npRef as a point:" << pRef;
Circle &cRef = cy1;
cout << "\ncRef as a Circle:" << cRef;
return 0;
}
注:
可以在一个工程里设计多个头文件 “point.h”、“cylinder.h”、“circle.h”分别定义各个类。
再设计 “point.cpp” 、“circle.cpp”, “cylinder.h”多个源程序文件分别定义各个类的成员函数。
设计一个主函数,通过对象访问类的成员函数。
在一个类中不能定义两个名字相同,参数个数和类型都相同的函数。在类家族中,不同层次的类可以出现名字相同,参数个数和类型都相同而功能不同的函数。编译系统按照同名覆盖的原则决定调用哪个函数。
虚函数的工作原理是在派生类中定义与基类函数同名的函数,通过基类指针或引用来访问基类或派生类中的同名函数。上一节所述的赋值兼容性规则是多态性的基础。同一个函数可以统一处理具有公有派生关系的基类的对象和派生类对象。
虚函数的使用方法:
class Student
{
public:
Student(int, string, float);
virtual void display();
protected:
int num;
string name;
float score;
};
Student::Student(int n, string nam, float s)
{
num = n; name = nam; score = s;
}
void Student::display()
{
cout << "num:" << num << "\nname:" << name << "\nscore:"<< score << "\n\n";
}
class Graduate :public Student
{
public:
Graduate(int, string, float, float);
void display();
private:
float pay;
};
Graduate::Graduate(int n, string nam, float s, float p):Student(n, nam, s), pay(p) {}
void Graduate::display()
{
cout << "num:" << num << "\nname:" << name << "\nscore:"<< score << "\npay=" << pay << endl;
}
int main()
{
Student stud1(1001, "Li", 87.5);
Graduate grad1(2001, "Wang", 98.5, 563.5);
Student *pt = &stud1;
pt->display();
pt = &grad1;
pt->display();
return 0;
}
函数重载处理的是同一层次中的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题。前者要求函数名相同,但函数的参数或类型不同;后者要求的是不仅函数名相同,而且函数参数和类型也要相同。
多态性的两种理解:(1)具有不同功能的函数可以用同一个函数名,可以用一个函数名调用不同内容的函数。(2)发出同样的消息(对成员函数的调用)被不同类型的对象接收时导致完全不同的行为(不同的函数实现)。
静态多态的实现方式有函数重载和运算符重载;
动态多态的实现主要通过虚函数。
当类中任何函数前带virtual 时,编译程序将暗自在类中增加一个数据成员,在这称它为 VPTR ,它是一个指向函数指针表的指针,因此,With virtual类对象占用的内存空间比No virtual类的对象大。
class base
{
public:
virtual void Fn(int x){cout<< “在基类内x=”<<x<<endl; }
};
class subclass:public base
{
public:
virtual void Fn( float x){cout<< “在派生类内x=”<<x<<endl; }
};
void test (base & b)
{
int i=1;
b.Fn( i );
float f=2.0;
b.Fn( f );
}
void main()
{
base bc;
subclass sc;
cout<<“调用test(bc) \n”;
test(bc);
cout<<“调用test(sc) \n”;
test(sc);
}
注:只能将类的成员函数声明为虚函数。一个成员函数被声明为虚函数后,在同一类族不能再定义一个与该虚函数相同的非虚函数。
声明虚函数时,主要考虑以下几点:
class base
{
protected:
int x;
public:
base(int a) { x = a; }
virtual void print(){ cout << "base = "<< x << endl; }
};
class subclass :public base
{
public:
subclass(int a):base(a){}
virtual void print(){ cout << "First derivation\n"<< x << endl; }
};
class sub2class :public base
{
public:
sub2class(int a) :base(a) {}
virtual void print() { cout << "Second derivation\n" << x << endl; }
};
int main()
{
base *p, obj1(1);
subclass obj2(2);
sub2class obj3(3);
p = &obj1;
p->print();
p = &obj2;
p->print();
p = &obj3;
p->print();
obj2.print();
obj3.print();
return 0;
}
当派生类的对象撤销时一般先调用派生类的析构函数,然后调用基类的析构函数。当用new运算符建立一个动态对象时,如基类中有析构函数,并且定义了一个指向基类的指针变量。在程序中用带指针参数的delete运算符撤销对象时,系统只会执行基类的析构函数,而不执行派生类的析构函数。
将基类的析构函数声明为虚函数:
先调用派生类的析构函数,再调用基类的析构函数。当基类的析构函数为虚函数时无论指针指的是同一类族中的哪一个类对象,撤销对象时,系统会采用动态关联,调用相应的析构函数。
如果基类的析构函数声明为虚函数,那么其所有派生类的析构函数也自动成为虚函数。
构造函数不能声明为虚函数。
class Point
{
public:
Point() { cout << "Point Cons!" << endl; }
virtual ~Point() { cout << "Executing Point destructor" << endl; }
};
class Circle :public Point
{
public:
Circle() { cout << "Circle Cons!" << endl; }
~Circle() { cout << "Executing Circle destructor" << endl; }
private:
int radius;
};
int main()
{
Point *p = new Circle;
delete p;
Circle *pc = new Circle;
delete pc;
return 0;
}
何时需要虚析构函数:
只声明,不定义的虚函数称为纯虚函数。
纯虚函数一般格式:virtual 函数类型 函数名(参数表)=0;
纯虚函数的作用:
在许多情况下,基类中不能为虚函数给出一个有意义的定义,而将它说明为纯虚函数,其作用是:为派生类提供一个一致的接口(界面)。它的定义留给派生类来做,派生类根据需要来定义各自的实现。
如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数进行定义(实现),则该虚函数在派生类中仍然为纯虚函数。
注:1.一个类可以说明一个或多个纯虚函数。2.纯虚函数没有函数体,不能被调用,所在的抽象类,也不能直接进行实例化。3.空的虚函数,函数体为空。但所在的类可以实例化。
带有纯虚函数的类是抽象类,抽象类刻画了一组子类的公共操作接口的通用语义,这些接口的语义也传给子类。一般而言,抽象类只描述这组子类共同操作接口,而完整的实现留给子类。
抽象类的说明:
class Shape
{
public:
virtual float area() const { return 0.0; }
virtual float volume() const { return 0.0; }
virtual void shapeName() const = 0;
};
class Point :public Shape
{
protected:
float x, y;
public:
Point(float a = 0, float b = 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 &, const Point &);
};
ostream & operator<<(ostream &output, const Point &p)
{ output << "[" << p.x << "," << p.y << "]"; return output; }
class Circle :public Point
{
protected:
float radius;
public:
Circle(float x = 0, float y = 0, float r = 0);
void setRadius(float r) { radius = r; }
float getRadius() const { return radius; }
virtual float area() const;
virtual void shapeName() const { cout << "Circle:"; }
friend ostream &operator<<(ostream &, const Circle &);
};
Circle::Circle(float a, float b, float r) :Point(a, b), radius(r) {}
float Circle::area() const { return 3.14159*radius*radius; }
ostream &operator<<(ostream &output, const Circle &c)
{
output << "[" << c.x << "," << c.y << "], r=" << c.radius;
return output;
}
class Cylinder :public Circle
{
public:
Cylinder(float x = 0, float y = 0, float r = 0, float h = 0);
void setHeight(float h) { height = h; }
float getHeight() const { return height; }
virtual float area() const;
virtual float volume() const;
virtual void shapeName() const { cout << "Cylinder:"; }
friend ostream& operator<<(ostream&, const Cylinder&);
protected: float height;
};
Cylinder::Cylinder(float a, float b, float r, float h):Circle(a, b, r), height(h) {}
float Cylinder::area() const
{
return 2 * Circle::area() + 2 * 3.14159*radius*height;
}
float Cylinder::volume() const { return Circle::area()*height; }
ostream &operator<<(ostream &output, const Cylinder& cy)
{
output << "[" << cy.x << "," << cy.y << "], r=" << cy.radius<< ", h=" << cy.height;
return output;
}
int main()
{
Point point(3.2, 4.5);
Circle circle(2.4, 12, 5.6);
Cylinder cylinder(3.5, 6.4, 5.2, 10.5);
point.shapeName(); //静态关联
cout << point << endl;
circle.shapeName();
cout << circle << endl;
cylinder.shapeName();
cout << cylinder << endl << endl;
Shape *pt;
pt = &point; //动态关联
pt->shapeName();
cout << "x=" << point.getX() << ",y="<< point.getY() << "\narea="<< pt->area() << "\nvolume="<< pt->volume() << "\n\n";
pt = &circle;
pt->shapeName();
cout << "x=" << circle.getX()<< ",y=" << circle.getY() <<"\narea=" << pt->area() <<"\nvolume=" << pt->volume()<< "\n\n";
pt = &cylinder;
pt->shapeName();
cout << "x=" << cylinder.getX()<< ",y=" << cylinder.getY()<< "\narea=" << pt->area()<< "\nvolume=" << pt->volume() << "\n\n";
return 0;
}