• C++学习之路—继承与派生(四)拓展与总结


    (根据《C++程序设计》(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明)

        1    拓展部分

    本节主要由两部分内容组成,分别是(1)基类与派生类的转换和(2)继承与组合

    1.1    基类与派生类的转换

        在前几篇博客中,可以看到在3种继承方式中,只有公有继承能较好的保留基类的特征,它保留了除构造函数和析构函数以外所有的基类成员,基类的公有或保护成员的访问权限在派生类中全部保留了下来,在派生类外可以调用基类的公有成员函数以访问基类的私有成员。而非公用派生类不能实现基类的全部功能(例如在派生类外不能调用基类的公有成员函数访问基类的私有成员)。因此,只有公有派生类才是基类真正的子类型,它完整的继承了基类的功能

        如果不同类型数据之间可以自动转换和赋值,成为赋值兼容。基类与公用派生类之间具有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子对象代替。基类与(公用)派生类之间的赋值兼容表现在以下4个方面:

    (1)派生类对象可以向基类对象赋值。若类B是类A的公用派生类,则可进行以下操作:

       1: A a1 ;     //定义基类A对象a1
       2: B b1 ;     //定义公用派生类B的对象b1
       3: a1 = b1 ;   //用派生类B的对象b1对基类对象a1赋值

    在进行赋值时舍弃派生类自己新增加的成员,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。赋值后不能试图通过对象a1去访问派生类对象b1新增的成员,假设age是派生类B中新增加的成员,则:

       1: a1.age = 23 ;  //错误,a1中不包含派生类中增加的成员
       2: b1.age = 21 ;   //正确,b1中包含派生类中增加的成员

    总结:只能用子类对象对基类对象赋值,而不能用基类对象对其子类对象赋值。同一基类的不同派生类对象之间也不能赋值。

    (2)派生类对象可以代替基类对象向基类对象的引用进行赋值或初始化。

    如果已经定义了基类A对象a1,可以定义a1的引用变量:

       1: A a1 ;           //定义基类A对象a1
       2: B b1 ;         //定义公用派生类B对象b1
       3: A& r = a1 ;    //定义基类A对象的引用r,并用a1对其初始化
       4: //也可以将上面最后一行改为
       5: A& r  = b1 ;    //定义基类A对象的引用r,并用派生类B对象b1对其初始化

    此时r并不是b1的别名,也不是与b1共享同一段存储单元。它只是b1中基类部分的别名,r与b1中基类部分共享同一段存储单元,r与b1具有相同的起始地址。

    (3)如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。例如:

       1: void fun ( A& r )                //形参是A类对象的引用
       2: {
       3:     cout << r.num << endl ;      //输出该引用中的数据成员num
       4: }
       5: B b1 ;                           //定义公用派生类的对象b1
       6: fun( b1 ) ;                      //输出B对象b1的基类的数据成员num的值

    在fun函数中只能输出派生类中基类成员的值。

    (4)派生类对象的地址可以赋给指向基类对象的指针变量,即指向基类对象的指针变量可以指向派生类对象。示例程序如下:

       1: #include <iostream>
       2: #include <string>
       3: using namespace std ;
       4:  
       5: class Student                                //声明Student类
       6: {
       7: public:
       8:     Student ( int , string , float ) ;       //声明构造函数
       9:     void display();                          //声明输出函数
      10: private:
      11:     int num ;
      12:     string name ;
      13:     float score ;
      14: };
      15: Student::Student( int n , string nam , float s )  //定义构造函数
      16: {
      17:     num = n ;
      18:     name = nam ;
      19:     score = s ;
      20: }
      21: void Student::display()                           //定义输出函数
      22: {
      23:     cout << endl << "num:" << num << endl ;
      24:     cout << "name:" << name << endl ;
      25:     cout << "score:" << score << endl ;
      26: }
      27:  
      28: class Graduate : public Student                //声明公用派生类Graduate
      29: {
      30: public:
      31:     Graduate( int , string , float , float ) ; //声明构造函数
      32:     void display() ;                           //声明输出函数
      33: private
      34:     float wage ;
      35: };
      36: Graduate::Graduate( int n , string nam , float s , float w ) : Stident( n , nam , s )
      37:                     , wage( w ) {}            //定义构造函数
      38: void Graduate::display()                      //定义输出函数
      39: {
      40:     Student::display() ; 
      41:     cout << "wage:" << wage << endl ;
      42: }
      43:  
      44: int main()
      45: {
      46:     Student stud1( 1001 , "Li" , 87.5 ) ;     //定义Student类对象stud1
      47:     Graduate grad1( 2001 , "wang" , 98.5 , 1000 ) ;  //定义Graduate类对象grad1
      48:     Student *pt = &stud1 ;                    //定义指向基类的指针并指向stud1
      49:     pt->display() ;                           //调用stud1.display函数
      50:     pt = &grad1 ;                            //指针指向grad1
      51:     pt->display() ;                          //调用grad1.display函数
      52:  
      53:     return 0 ;
      54: }

    先看一下程序运行结果,再进行具体的分析:

    无标题

    分析:有很多读者会认为,在派生类中有两个同名的display成员函数,根据同名覆盖的规则,第二次被调用的应当是派生类Graduate对象的display函数,在执行Graduate::display函数过程中调用Student::display函数,输出num,name,score,然后再输出wage的值。很明显与上述结果不符,why?问题在于pt是指向Student类对象的指针变量,即使让它指向了grad1,但实际上pt指向的是从grad1从基类继承的部分。通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。

        通过本例可以看到,用指向基类对象的指针变量指向子类对象是合法的、安全的,不会出现编译上的错误。但人们更希望通过使用基类指针能够调用基类和子类对象的成员,要解决这个问题,就要用到以后会讲到的多态性和虚函数。

    1.2   继承与组合

    在一个类中以另一个类的对象作为数据成员的,称为类的组合。例如,声明Professor类是Teacher类的派生类,另有一个类BirthDate,包含year,month,day等数据成员。我们可以将教授生日的信息加入到Professor类的声明中。如:

       1: class Teacher                    //声明教师类
       2: {
       3: public:
       4:     ...
       5: private: 
       6:     int num ;
       7:     string name ;
       8:     char sex ;
       9: };
      10:  
      11: class Birthdate                  //声明生日类
      12: {
      13: public:
      14:     ...
      15: private:
      16:     int year ;
      17:     int month ;
      18:     int day ;
      19: };
      20:  
      21: class Professor : public Teacher   //声明教授类
      22: {
      23: public:
      24:     ...
      25: private:
      26:     Birthdate birthday ;           //Birthdate类的对象作为数据成员
      27: };

         类的组合和类的继承一样,都是有效地利用已有类的资源。但二者的概念和用法不同。通过继承建立了派生类与基类的关系,这是一种“is-a”的关系,如“白猫是猫”,派生类是基类的具体化的实现,是基类的一种。通过组合则建立了成员类和组合类的关系,它们之间是“has-a”的关系。不能说Professor是一个Birthdate,只能说教授有一个Birthdate的属性。

     

       2   继承在软件开发中的重要意义

        缩短软件开发过程的关键是鼓励软件的重用。继承机制在很大一部分上解决了这个问题。编写面向对象的程序时要把注意力放在实现对自己有用的类上面,对已有的类加以整理和分类,进行剪裁和修改,并在此基础上集中精力编写派生类新增加的部分。

        人们为什么这么看重继承,要求在软件开发中使用继承机制,尽可能的通过继承建立一批新的类,有以下几个原因:

    (1)有许多基类是被程序的其他部分或其他程序使用的,这些程序要求保持原有的基类不受破坏。

    (2)用户往往得不到基类的源代码。如果使用的类库,用户无法知道成员函数的代码,因此也就无法对基类进行修改,保证了基类的安全。

    (3)在类库中,一个基类可能已被指定与用户所需的多种组建有联系,因此类库不允许被修改。

    (4)许多类是专门被设计为基类的,并没有什么独立的功能,只是一个框架,或者说是抽象类。设计这些通用的类目的是建立通用的数据结构,以便用户在此基础上添加各种功能建立派生类。

  • 相关阅读:
    解决:vue/nodeprecatedvbindsync
    clearValidate elementui为什么没效果
    vue state vuex使用
    【转】解决iframe使用postMessage传值addEventListener未接收到却收到webpackwarning的问题
    elementui中eltable 显示空白 不显示
    在vue中使用lottie动画
    装饰模式(Decorator)
    模版方法(Template Method)
    Redis数据类型、Redis列表命令
    RabbitMQ、ErLang下载、RabbitMQ下载
  • 原文地址:https://www.cnblogs.com/hust-ghtao/p/3500928.html
Copyright © 2020-2023  润新知