C++ 学习之路(11):多态性与虚函数

来源:互联网 发布:南京软件企业排名 编辑:程序博客网 时间:2024/05/22 15:55

一、多态性:
1、概念:
①不同对象收到相同的消息时,产生不同的动作。即多态性。
②即用相同的接口访问不同功能的函数,实现“一种接口,多种
方法”
2、多态可分为编译时的多态和运行时的多态
①连编:即把函数名与函数体的程序代码连接(联系)在一起的过
程。
静态连编:即在编译阶段完成的连编。
动态连编:是运行阶段完成的连编。
②编译时的多态:通过静态连编来实现。静态连编时,系统用实参与形参进行匹配,对于同名的重载函数边根据参数上的差异进行区分,然后进行连编,从而实现多态性。
③运行时的多态:通过动态连编来实现。当程序调用到某一函数名时,才去寻找和连接其程序代码,
C++实际上是采用了静态连编和动态连编相结合的连编方法。

二、虚函数
1、虚函数是重载的另一种表现形式(动态重载的方式),它允许函数调用与函数体之间的联系在运行时才建立,即所谓的动态连编。
2、虚函数的定义在基类中进行。形式为:
virtual 返回类型 函数名(形参表)
{
函数体
}
3、虚函数在派生类中重新定义时,其函数原型(包括返回类型、函数名、参数个数、参数类型的顺序),都必须与基类中的原型完全相同。
4、C++规定,如果在派生类中,没有用virtual显示地给出虚函数声明,系统会按照以下规则判断一个成员函数是否为虚函数:
① 该函数与基类的虚函数有相同的名称。
② 该函数与基类的虚函数有相同的参数个数及相同的对应参数类型。
③ 该函数与基类的虚函数有相同的返回类型或者满足赋值兼容规则的指针、引用型的返回类型。
5、注:
① 虚函数使用的基础是赋值兼容规则,而赋值兼容规则成立的前提条件为派生类从其基类公有派生。
② 必须现在基类中定义虚函数。
③ 派生类对基类中声明的虚函数进行重定义时,virtual可写可不写。
④ 一个虚函数无论被公有继承多少次,仍保持其虚函数的特性。
⑤ 虚函数必须是其所在类的成员函数。
⑥ 内联函数不能是虚函数。
⑦ 构造函数不能是虚函数。

虚析构函数:
1、

#include <iostream>using namespace std;class Base{    public:        ~Base()        { cout<<"调用基类Base的析构函数"<<endl; }};class Derived:public Base{    public:        ~Derived()        { cout<<"调用派生类Derived的析构函数"<<endl; }};int main(){    Base *p;        // 定义指向基类Base的指针变量p     p = new Derived;// 用运算符new为派生类的无名对象动态地                     // 分配了一个存储空间,并将地址赋给对象指针p     delete p;       // 用运算符delete撤销无名对象,释放动态存储空间     return 0;}
程序运行结果为:调用基类Base的析构函数本程序只执行了基类Base的析构而没有执行派生类Derived的析构,是因为当撤销指针p所指的派生类的无名对象而调用析构函数时,采用了静态连编方式,只调用了基类Base的析构函数

2、 若希望先调用派生类析构再调用基类的析构可以把基类的析构生美味虚析构函数,一般格式为:
virtual ~类名()
{
函数体
};
虽然派生类的析构函数与基类的析构函数名字不同,但如果将基类的析构函数声明为虚析构函数,则由该基类派生出的所有派生类的析构函数都自动成为虚函数。

虚函数与重载函数的关系
1、普通的函数重载:重载时,其函数的参数或参数类型必须有所不同,函数的返回类型也可以不同。
2、虚函数的重载:当重载一个虚函数时,也就是说在派生类中重新定义虚函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类中的虚函数原型完全相同。
3、注:
虚函数重载时,如果仅仅返回类型不同,系统会报错;
如果仅仅函数名相同,而参数个数、类型、顺序不同,系统会将它视为普通函数重载,这是将失去虚函数的特性。
例:

#include <iostream>using namespace std;class Base{public:    virtual void f1();          virtual void f2();    virtual void f3();    void f4();};class Derived:public Base{public:    virtual void f1();      // f1()是虚函数,这里可不写virtual    void f2(int x);         // 与基类中的f2()作为普通函数重载,虚特性消失    /* char f3();   */      // 错误,因为与街垒中的f3()只有返回类型不同,应删去    void f4();              // 与基类中的f4()是普通函数重载,不是虚函数。                            // (当用Derived类的对象访问f4()时,基类f4()被隐藏。)};void Base::f1(){ cout<<"--Base1--"<<endl; }void Base::f2(){ cout<<"--Base2--"<<endl; }void Base::f3(){ cout<<"--Base3--"<<endl; }void Base::f4(){ cout<<"--Base4--"<<endl; }void Derived::f1(){ cout<<"--Derived f1--"<<endl; }void Derived::f2(int x){ cout<<"--Derived f2--"<<endl; }void Derived::f4(){ cout<<"--Derived f4--"<<endl; } int main(){    Base d1,*bp;    Derived d2;    bp=&d2;    bp->f1();       // 调用 Derived::f1()    bp->f2();       // 调用 Base::f2()    bp->f4();       // 调用 Base::f4()    return 0;}
基类Base指针bp指向公有派生类Derived,访问的是基类继承下来的成员函数或访问重构的虚函数。输出结果:--Derived f1----Base f2----Base f4--

多继承与虚函数
1、有例有真相

#include <iostream>using namespace std;// 普通类A1class A1{   public:       virtual void fun()   // 定义fun()为虚函数       { cout<<"--A1--"<<endl; } };// 普通类A2class A2{   public:       void fun()           // 定义fun()为普通的成员函数       { cout<<"--A2--"<<endl; }};// A1,A2的公有派生类Bclass B:public A1,public A2{   public:       void fun()                  { cout<<"--B--"<<endl; }};int main(){    A1 obj1,*ptr1;  // 定义指向基类A1的指针ptr1和基类A1的对象obj1    A2 obj2,*ptr2;  // 定义指向基类A2的指针ptr2和基类A2的对象obj2    B obj3;         // 定义派生类B的对象obj3    ptr1 = &obj1;   // 指针ptr1指向对象obj1    ptr1->fun();    // 调用基类A1的函数fun()    ptr2 = &obj2;   // 指针ptr2指向对象obj2    ptr2->fun();    // 调用基类A2的函数fun()    ptr1 = &obj3;   // 指针ptr1指向对象obj3    ptr1->fun();    // 此处的fun()为虚函数,因此调用派生类B的函数fun()    ptr2 = &obj3;   // 指针ptr2指向对象obj3    ptr2->fun();    // 此处的fun()为非虚函数,而ptr2又为基类A2的指针,                    // 因此调用基类A2的函数fun()    return 0;}
运行结果为:--A1----A2----B----A2--

纯虚函数和抽象类
纯虚函数:
1、有时,基类往往表示一种抽象的概念,它并不于具体的事物相联系。此时在基类中将某一成员函数定义为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义。为此,C++加入了纯虚函数的概念。
2、声明纯虚函数的一般形式为:
virtual 函数类型 函数名(参数表)=0;
3、声明为纯虚函数之后,基类中就不再给出函数的实现部分。
4、纯虚函数没有函数体,它最后面的“=0”并不表示函数的返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”、
5、纯虚函数不具备函数的功能,不能被调用。
有例有真相:

#include <iostream>using namespace std;// 声明基类Circleclass Circle{   public:       void setr(int x) { r=x; }       virtual void show()=0;   // 定义纯虚函数show()   protected:       int r;};// 声明派生类Areaclass Area:public Circle{   public:       void show()      // 重定义虚函数show(),用于求圆的面积       { cout<<"Area is "<<3.14*r*r<<endl; }};// 声明派生类Perimeterclass Perimeter:public Circle{   public:         void show()      // 重定义虚函数show(),用于求圆的周长       { cout<<"Perimeter is "<<2*3.14*r<<endl; }};int main(){    Circle *ptr;    // 定义基类指针ptr    Area ob1;       // 定义Area类对象ob1    Perimeter ob2;  // 定义Perimeter类对象ob2    ob1.setr(10);   // 初始化r     ob2.setr(10);    ptr=&ob1;    ptr->show();    // 计算圆的面积    ptr=&ob2;    ptr->show();    // 计算圆的周长    return 0;}

抽象类:
1、如果一个类至少有一个纯虚函数,那么就称该类为抽象类。
2、定义抽象类的唯一目的是用它作为基类去建立派生类。
3、抽象类作为一种基本类型提供给用户,用户在这个基础上根据自己的需要定义出功能各异的派生类,并用这些派生类去建立对象。
4、对于抽象类的使用有以下规定:
① 有序抽象类中至少包含一个没有定义功能的纯虚函数。因此抽象类只能作为其他类的基类来使用,不能建立抽象类对象。
② 不允许从具体类中派生出抽象类。所谓具体类即不包含纯虚函数的普通类。
③ 抽象类不能用作函数的参数类型、函数的返回类型或显示转换的类型。
④ 可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。
⑤ 如果派生类中没有定义纯虚函数的实现,而派生类只是继承基类的纯虚函数,则这个派生类仍然是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。

总结:
有例有真相:
① 应用C++的多态性,计算三角形、矩形、和圆的面积

#include <iostream>using namespace std;// 定义一个公共基类class Figure{   public:       Figure(double a,double b)       { x=a; y=b; }       virtual void show_area()     // 定义一个虚函数,作为界面接口       {           cout<<"No area computataion defined ";           cout<<"for this class."<<endl;       }   protected:       double x,y;};// 定义三角形派生类class Triangle:public Figure{   public:       Triangle(double a,double b):Figure(a,b)       { };       void show_area()     // 虚函数重定义,用作求三角形的面积       {           cout<<"Triangle with height "<<x;           cout<<" and base "<<y<<" has an area of ";           cout<<x*y*0.5<<endl;       }};// 定义矩形派生类class Square:public Figure{   public:       Square(double a,double b):Figure(a,b)       { };       void show_area()     // 虚函数重定义,用作求矩形的面积       {           cout<<"Square with dimension "<<x;           cout<<" * "<<y<<" has an area of ";           cout<<x*y<<endl;       }};// 定义圆派生类class Circle:public Figure{   public:       Circle(double a):Figure(a,a)       { };       void show_area()     // 虚函数重定义,用作求圆的面积       {           cout<<"Circle with raidus "<<x;           cout<<" has an area of ";           cout<<x*x*3.1416<<endl;       }};int main(){    Figure *p;      // 定义基类指针p    Triangle t(10.0,6.0);   // 定义三角形类对象t    Square s(10.0,6.0);     // 定义矩形类对象s    Circle c(10.0);         // 定义圆形类对象c    p=&t;    p->show_area();         // 计算三角形面积    p=&s;    p->show_area();         // 计算矩形面积    p=&c;    p->show_area();         // 计算圆形面积    return 0;}
运行结果:Triangle with height 10 and base 6 has an area of 30Square with dimension 10 * 6 has an area of 60Circle with radius 10 has an area of 314.16

② 应用抽象类、求圆、圆内接正方形和圆外切正方形的面积和周长
有例有真相:

#include <iostream>using namespace std;// 声明一个抽象类Shapeclass Shape{   protected:       double r;   public:       Shape(double x)       { r=x; }       virtual void area()=0;       // 纯虚函数       virtual void perimeter()=0;  // 纯虚函数};// 声明派生类Circleclass Circle:public Shape{   public:       Circle(double x):Shape(x)       { }       void area();         // 在派生类Circle中,声明虚函数area()       void perimeter();    // 在派生类Circle中,声明虚函数perimeter()};void Circle::area()         // 在派生类Circle中,定义虚函数area(){    cout<<"The circle's area is ";    cout<<3.14*r*r<<endl;}void Circle::perimeter()    // 在派生类Circle中,定义虚函数perimeter(){    cout<<"The circle's perimeter is ";    cout<<2*3.14*r<<endl;}// 声明派生类In_Squareclass In_Square:public Shape{   public:       In_Square(double x):Shape(x)       { }       void area();         // 在派生类In_Square中,声明虚函数area()       void perimeter();    // 在派生类In_Square中,声明虚函数perimeter()};void In_Square::area()      // 在派生类In_Square中,定义虚函数area(){    cout<<"The internal square's area is ";    cout<<2*r*r<<endl;}void In_Square::perimeter() // 在派生类In_Square中,定义虚函数perimeter(){    cout<<"The internal square's perimeter is ";    cout<<4*1.414*r<<endl;}// 声明派生类Ex_Squareclass Ex_Square:public Shape{   public:       Ex_Square(double x):Shape(x)       { }       void area();         // 在派生类Ex_Square中,声明虚函数area()       void perimeter();    // 在派生类Ex_Square中,声明虚函数perimeter()};void Ex_Square::area()      // 在派生类Ex_Square中,定义虚函数area(){    cout<<"The external square's area is ";    cout<<4*r*r<<endl;}void Ex_Square::perimeter() // 在派生类Ex_Square中,定义虚函数perimeter(){    cout<<"The external square's perimeter is ";    cout<<8*r<<endl;}int main()  {                       Shape *ptr;         // 定义抽象类Shape的指针ptr    Circle ob1(6);      // 定义派生类Circle的对象ob1    In_Square ob2(6);   // 定义派生类In_Square的对象ob2    Ex_Square ob3(6);   // 定义派生类Ex_Square的对象ob3    ptr=&ob1;           // 指针ptr指向圆类Circle的对象ob1    ptr->area();        // 求圆的面积    ptr->perimeter();   // 求圆的周长    ptr=&ob2;           // 指针ptr指向圆内接正方形类In_Square的对象ob2    ptr->area();        // 求圆内接正方形的面积    ptr->perimeter();   // 求圆内接正方形的周长    ptr=&ob3;           // 指针ptr指向圆外切正方形类Ex_Square的对象ob3    ptr->area();        // 求圆外切正方形的面积    ptr->perimeter();   // 求圆外切正方形的周长    return 0; }
运行结果为:The circle's area is 113.04The circle's perimeter is 37.68The internal square's area is 72The internal square's perimeter is 33.936The external square's area is 144The external square's perimeter is 48抽象类和虚函数使程序的扩充变得非常容易。例如,在上述程序中,通过在main()函数前增加下述派生类的定义,即可增加一个计算圆外切三角形面积和周长的功能
class Ex_Triangle:public Shape{   public:       Ex_Triangle(double x):Shape(x)       { }       void area();       void perimeter();};void Ex_Triangle::area(){    cout<<"The triangle's area is ";    cout<<3*1.732*r*r<<endl;}void Ex_Triangle::perimeter(){    cout<<"The triangle's perimeter is ";    cout<<6*1.732*r<<endl;}
如果在main()函数中增加下述几条语句:
Ex_Triangle ob4(6);    ptr=&ob4;    ptr->area();    ptr->perimeter();
程序运行后,即可打印出相应圆外切三角形的面积和周长。

End….

0 0
原创粉丝点击