代码改变世界

C++赋值兼容规则和多态

2011-08-11 23:28  gavin's world  阅读(872)  评论(0编辑  收藏  举报

 赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员,而且所有成员的访问控制属性也和基类完全相同。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。赋值兼容规则中所指的替代包括以下的情况: 
  ·派生类的对象可以赋值给基类对象。
  ·派生类的对象可以初始化基类的引用。
  ·派生类对象的地址可以赋给指向基类的指针。
  在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
  如果B类为基类,D为B类的公有派生类,则D类中包含了基类B中除构造、析构函数之外的所有成员,这时,根据赋值兼容规则, 在基类B的对象可以使用的任何地方,都可以用派生类的对象来替代。在如下程序中, b1为B类的对象,d1为D的对象。
    clasS B
    {…}
    class D:public B
    {…}
    B b1,*pbl;
    D d1;

  这时:
  ①派生类对象可以赋值给基类对象,即用派生类对象中从基类继承来的成员,逐个赋值给基类对象的成员:
    b1=d1;
  ②派生类的对象也可以初始化基类对象的引用:
    B &bb=d1;
  ③派生类对象的地址也可以赋给指向基类的指针:
    pb1=&d1;
  由于赋值兼容规则的引入,对于基类及其公有派生类的对象,我们可以使用相同的函数统一进行处理(因为当函数的形参为基类的对象时, 实参可以是派生类的对象), 而没有必要为每一个类设计单独的模块,从而大大提高了程序的效率。 这正是C++的又一重要特色,即多态性,可以说,赋值兼容规则是多态性的重要基础之一。
  下面我们来看一个例子,例中使用同样的函数对同一个类族中的对象进行操作。


类的派生关系图

 

  例:赋值兼容规则实例。
  如图所示,本例中,基类B0以公有方式派生出B1类,B1类再作为基类以公有方式派生出D1类,基类B0中定义了成员函数display(), 在派生类中对这个成员函数进行了覆盖。程序代码如下:
    //** chap13_2.cpp
    #include<iostream.h>
    class B0 //基类B0声明
    {
     publiC:
      void display(){cout<<"BO::display()"<<endl;} 

         //公有成员函数
    };
    class B1:publicB0 //公有派生类B1声明
    {
     public:
      void display(){cout<<"Bi::display()"<<endl;} 

         //公有成员函数
    };
    class D1:public B1 //公有派生类B1声明
    {
     public:
      void display(){cout<<”D1::display()”<<endl;}
          
//公有成员函数
    };
    void fun(BO *ptr) //普通函数
    { //参数为指向基类对象的指针
     ptr->display(); //”对象指针—>成员名”
    }
    void main() //主函数
    {
     BO bO; //声明B0类对象
     B1 bl; //声明B1类对象

     D1 dl; //声明D1类对象
     BO *p; //声明B0类指针
     p=&bO; //BO类指针指向B0类对象
     fun(p);
     p=&b1; //BO类指针指向B1类对象
     fun(p);
     p=&d1; //BO类指针指向D土类对象
     fun(p);
    }

  这样,通过“对象名.成员名”或者“对象指针->成员名”的方式,就可以访问到各派生类中新添加的同名成员。虽然根据赋值兼容原则,可以将派生类对象的地址赋值给基类B0的指针,但是通过这个基类类型的指针,却只能访问到从基类继承的成员。
  在程序中,定义了一个形参为基类B0类型指针的普通函数fun, 根据赋值兼容规则,可以将公有派生类对象的地址赋值给基类类型的指针,这样,使用fun函数就可以统一对这个类族中的对象进行操作。 程序运行过程中,分别把基类对象、 派生类B1的对象和派生类D1的对象赋值给基类类型指针p,但是,通过指针p,只能使用继承下来的基类成员。 也就是说,尽管指针指向派生类D1的对象,fun函数运行时通过这个指针只能访问到D1类从基类B0继承过来的成员函数display,而不是D1类自己的同名成员函数。因此,主函数中三次调用函数fun的结果是同样的——访问了基类的公有成员函数。程序的运行结果为:

    B0::display
    B0::display
    B0::display


  通过这个例子,我们看到,根据赋值兼容规则,我们可以在基类出现的场合使用派生类进行替代,但是替代之后派生类仅仅发挥出基类的作用。多态的设计方法可以保证在赋值兼容的前提下,基类、派生类分别以不同的方式来响应相同的消息。
  运行过程中的多态需要满足三个条件:首先类之间应满足赋值兼容规则,其二是要声明虚函数,第三是要由成员函数来调用或者是通过指针、引用来访问虚函数。如果是使用对象名来访问虚函数,则联编在编译过程中就可以进行(静态联编),而无需在运行过程中进行。 
  这个程序是由chap13_2.cpp“赋值兼容规则举例”改进而来。在基类B0中将原有成员display()声明为虚函数,其他部分都没有做任何修改。与例chap13_2.cpp不同的是,在使用基类类型的指针时,它指向哪个派生类的对象,就可以通过它访问那个对象的同名虚成员函数。
    #include<iostream.h>
    class BO //基类B0声明
    {
     public: //外部接口
      virtual void display(){
        cout<<"BO::display()”<<endl;} //虚成员函数
    };
    class Bl:public BO //公有派生
    {
     public:
      void display(){cout<<"Bl::display()”<<endl;}
         
//虚成员函数
    };
    class Dl:public Bl //公有派生
    {
     public:
      void display(){cout<<”D1::display()”<<endl;}
         
//虚成员函数
    };
    void fun(BO*ptr) //普通函数
    {
     ptr->display();

    }
    void main() //主函数
    {
     BO b0,*p; //声明基类对象和指针
     B1 b1; //声明派生类对象
     D1 d1; //声明派生类对象
     P=&b0;
     fun(p); //调用基类B0函数成员
     p=&b1;
     fun(p); //调用派生类B1函数成员
     p=&d1;
     fun(p); //调用派生类Dl函数成员
    }

  程序中类B0、B1和D1属于同一个类族,而且是通过公有派生而来,因此满足赋值兼容规则。同时,基类B0的函数成员display声明为虚函数, 程序中使用对象指针来访问函数成员,这样联编过程就是在运行中完成,实现了运行中的多态。通过基类类型的指针就可以访问到正在指向的对象的成员,这使得能够对同一类族中的对象进行统一的处理,抽象程度更高,程序更简洁、更高效。程序的运行结果为:
    B0::display()
    B1::display()
    D1::display()

  在本程序中,派生类并没有显式给出虚函数声明,这时系统就会遵循以下的规则来判断一个函数成员是不是虚函数:
  ·该函数是否与基类的虚函数有相同的名称;
  ·该函数是否与基类的虚函数有相同的参数个数及相同的对应参数类型;
  ·该函数是否与基类的虚函数有相同的返回值或者满足赋值兼容规则的指针、引用型的返回值;
  如果从名称、参数及返回值三个方面检查之后,派生类的函数满足了上述条件,就被自动确定为虚函数。上述程序就是由系统自动判断确定的。