美文网首页
十五、继承和派生,兼容性原则

十五、继承和派生,兼容性原则

作者: 木鱼_cc | 来源:发表于2018-06-09 15:20 被阅读0次
    1. 类和类的关系
    2. 继承
      2.1 定义
      2.2 派生类的组成
      2.3 几点说明
    3. 继承的方式
      3.1 protected 访问控制
      3.2 派生类成员的标识和访问
    4. 练习
    5. 继承中的构造和析构
      5.1 类型兼容性原则
      5.2 初始化父类成员,父类与子类的构造函数的关系
      5.3 继承中同名成员变量处理方法
      5.4 派生类中的static关键字
      5.5 多继承

    在 C++中可重用性(software reusability)是通过继承(inheritance)这一机制来实现的。 如果没有掌握继承性,就没有掌握类与对象的精华。

    1.类和类之间的关系

    has-A uses-A is-A

    • has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
    • uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
    • is-A 机制称为“继承”。关系具有传递性,不具有对称性。

    2.继承

    #include <iostream>
    #include <string>
    using namespace std;
    class Student
    {
       public:
       Student(int age,string name){
            this->age = age;
            this->name = name;
        }
    
        void printS(){
          cout<<name<<endl;
          cout<<age<<endl;
        }
       private:
       string name;
       int age;
    };
    
    //类Student2继承与类Student
    class Student2:public Student
    {
      public:
        Student2(int age,string name,char sex,float score):Student(age,name){//私有变量无法继承,只能通过初始化传递!
    //子类继承过来的成员变量,通过父类的构造器来构造
           this->sex = s;
           this->score = f;
         }
      void printS(){
         Student::printS();
         cout<<this->sex<<endl;
         cout<<this->score<<endl;
        }
      private:
      char sex;
      float score;
    };
    
    
    2.1定义

    类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。

    派生与继承,是同一种意义两种称谓。isA 的关系。

    1.png
    2.2派生类的组成

    派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。

    2.png
    2.3 几点说明:
    • 全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基类是需设计的。
    • 派生类有了自己的个性,使派生类有了意义。

    3.继承的方式

    class 派⽣生类名:[继承⽅方式] 基类名{
        派⽣生类成员声明;
    };
    

    一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类, 称为单继承。下面从单继承讲起。

    3.1protected 访问控制

    protected 对于外界访问属性来说,等同于私有,但可以派生类中可见。

    #include <iostream>
    using namespace std;
    class Base {
     public://访问控制权限
        int pub;//对内部外部均可以
     protected:
        int pro;//对内可以,外不可以//protected访问控制权限下的成员 儿子可见
     private:
        int pri;//对内外 均不可以
    };
    
    class Drive:public Base{
      public:
         void  func(){
            pub = 10;
            pro = 100;
           //pri=1000;//error
         }
    };
    
    //三看原则
    //1 看当前的成员调用的是类的外部 还是在类的内部
    //2 看儿子的继承方式,是公有继承还是 私有继承
    //3 看当前的额成员变量在父亲中的访问控制权限
    
    
    //1 基类的私有成员 不管子类如何继承,子类都访问不了。
    //2 如果是公有继承,那么基类中的访问控制权限,除了私有成员,在子类中保持不变
    //3 如果是保护继承protected,那么子类中除了基类的私有成员,全部是protected权限
    //4 如果是私有继承private,父类中除了私有成员,在子类都是私有成员
    int main(void)
    {
        Base b;
        b.pub = 10;
      //b.pro = 100;//error
      //b.pri = 1000;//error
    
       return 0;
    }
    
    3.2派生类成员的标识和访问
    ---- public protected private
    公有继承(public) public protected 不可见
    保护继承(protected) protected protected 不可见
    私有继承(private) private private 不可见

    public公有继承
    当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类 中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无 论派生类的成员还是派生类的对象都无法访问基类的私有成员。

    private私有继承
    当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。。基类的公有成员和保 护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但 是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对 象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都 会成为不可访问。因此私有继承比较少用。

    protect保护继承
    保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的 公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员 还是派生类的对象,都无法访问基类的私有成员。

    private成员在子类中依然存在,但是却无法访问到。不论何种方式继承基类,派生类都不能直接使用基类的私有成员。

    如何恰当的使用public,protected和private为成员声明访问级别?

    • 需要被外界访问的成员直接设置为public
    • 只能在当前类中访问的成员设置为private
    • 只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。

    4.练习

    #include <iostream>
    using namespace std;
    
    class A
    {
    private:
        int a;
    protected:
        int b;
    public:
        int c;
        A(){
          a = 0;
          b = 0;
          c = 0;
         }
       void set(int a,int b,int c){
         this­‐>a = a;
         this­‐>b = b;
         this‐>c = c;
       }
    };
    
    
    class B:public A
    {
    public:
        void print()
        {
          cout<<"a = "<<a;//能否访问???  不可以
          cout<<"b = "<<b;//能否访问???  可以
          cout<<"c = "<<endl;//能否访问??? 可以
        }
    };
    
    class C:protected A
    {
    public:
        void print()
       {
         cout<<"a = "<<a;//能否访问???  不可以
         cout<<"b = "<<b;//能否访问???  可以
         cout<<"c = "<<endl;//能否访问??? 可以
       }
    };
    
    class D:private A
    {
    public:
       void print()
       {
         cout<<"a = "<<a;//能否访问???   不可以
         cout<<"b = "<<b<<endl;//能否访问???  不可以
         cout<<"c = "<<c<<endl;//能否访问??? 不可以
       }
    };
    
    int main(void)
    {
       A aa;
       B bb;
       C cc;
       D dd;
    
       aa.c = 100;//能否访问???  可以
       bb.c = 100;//能否访问???  可以
       cc.c = 100;//能否访问???  不可以
       dd.c = 100;//能否访问???  不可以
      
       aa.set(1,2,3);//能否访问???   可以
       bb.set(10,20,30);//能否访问???  可以
       cc.set(40,50,60);//能否访问???   不可以 set是protected,在外部不能访问
       dd.set(70,80,90);//能否访问??? 不可以
     
       bb.print();//能否访问???   可以
       cc.print();//能否访问???   可以
       dd.print();//能否访问???   可以
    
       return 0;
    }
    

    5.继承中的构造和析构

    5.1类型兼容性原则

    类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。

    类型兼容规则中所指的替代包括以下情况:

    • 子类对象可以当作父类对象使用
    • 子类对象可以直接赋值给父类对象
    • 子类对象可以直接初始化父类对象
    • 父类指针可以直接指向子类对象
    • 父类引用可以直接引用子类对象

    在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。

    子类就是特殊的父类 (base *p = &child;)

    /*
    子类对象可以当作父类对象使用
    子类对象可以直接赋值给父类对象
    子类对象可以直接初始化父类对象
    父类指针可以直接指向子类对象
    父类引用可以直接引用子类对象
    */
    
    class Parent
    {
    public:
       void printP(){
          cout<<"Parent::printP()..."<<endl;
       }
       int a;
    };
    
    
    class Child:public Parent
    {
    public:
       void printC(){
         cout<<"Child::printC()..."<<endl;
       }
    
       int b;
    };
    
    void print(Parent *p)
    {
       p->printP();
    }
    
    int main(void)
    {
        Child c;//子类对象
      
        c.printP(); //子类对象可以当作父类对象使用(方法继承)
    
       //子类对象可以直接赋值给父类对象
        Parent p = c;
      //Child c2 = p;报错!
      //由于子类用于父类的全部内存空间,子类能够保障父类初始化完成
      //子类对象可以直接初始化父类对象,因为子类内存空间包含的父类的内存空间,能够保证完全赋值
     
    
     //父类指针可以直接指向子类对象
     //子类对象能够完全满足父类指针的需求,所以可以
    Parent *pp = &c;//pp->printP,pp->printC不存在这个方法
    
    //不能用子类指针指向父类对象
    //父类对象满足不了子类指针的所有需求,所以不能够
    Child *cp = &p;//报错!cp->printP,cp->printC
    //cp指向父类对象,父类是没有printC方法的!所以报错
    
    
    //父类引用可以直接引用子类对象
      Parent &pr = c;
      
    //子类引用不可以引用父类的对象
      Child &cr = p;//报错,和父类指针一样
    
    //利用兼容性原则传参!
      print(&p);//Parent *p = &p;
      print(&c);//Parent *p = &c;
    
       return 0;
    }
    
    5.2初始化父类成员,父类与子类的构造函数有什么关系?
    3.jpg 4.png
    • 在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化.
    • 在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理.
    //子类在进行初始化成员变量的时候,如果此成员变量是继承过来的,那么需要调用父类的构造器来初始化
    class Parent
    {
    public:
      Parent(int a){
         this->a = a;
         cout<<"Parent(int a)..."<<endl;
       }
     ~Parent(){
         cout<<"~Parent()..."<<endl;
       }
    
      void printA(){
         cout<<"a = "<<a<<endl;
       }
    
    private:
       int a;
    }
    
    class Son:public Parent
    {
    public:
       //子类继承于父类,父类中的成员变量 应该用 父类的构造函数来初始化
       Son(int a,int b):Parent(a){
         this->b = b;
         cout<<"Son(int a,int b)...."<<endl;
       }
     
       ~Son(){
         cout<<"~Son()..."<<endl;
        }
    
      void printB(){
         cout<<"b = "<<b<<endl;
      }
    
      void printAB(){
        Parent::printA();
        this->printB();
     }
    private:
        int b;
    }
    
    void test1()
    {
       Son s(10,20);//先构造父类,再构造子类
       s.printAB();
    }//子类先析构,父类后析构,和栈一样!
    
    
    int main(void)
    {
     
       test1();
       return 0;
    }
    
    

    1、子类对象在创建时会首先调用父类的构造函数
    2、父类构造函数执行结束后,执行子类的构造函数
    3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
    4、析构函数调用的先后顺序与构造函数相反

    5.3继承中同名成员变量处理方法
    • 当子类成员变量与父类成员变量同名时
    • 子类依然从父类继承同名成员
    • 在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
    • 同名成员存储在内存中的不同位置

    同名成员变量和成员函数通过作用域分辨符进行区分

    5.4派生类中的static关键字
    • 基类定义的静态成员,将被所有派生类共享
    • 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质(遵守派生类的访问控制)
    • 派生类中访问静态成员,用以下形式显式说明:
      类名::成员
      或通过对象访问
      对象名.成员
    class A
    {
    public:
       static int s;
    private:
    
    }
    
    
    int A::s = 0;//静态成员变量要在类的外部初始化
    
    class B:public A
    {
    public:
    private:
    
    }
    
    
    int main(void)
    {
       B b;
       cout<<b.s<<endl;//0
       b.s = 100;
       cout<<b.s<<endl;//100
    
       cout<<A::s<<endl;//也是100
    
    
       return 0;
    }
    
    • static函数也遵守3个访问原则
    • static易犯错误(不但要初始化,更重要的显示的告诉编译器分配内存)
    5.5多继承

    一个类有多个直接基类的继承关系称为多继承

    5.png
    class Funiture
    {
    public:
       int m;//材质
    };//如果子类虚继承本类,编译器会将父类中的成员,只拷贝一份
    
    
    //沙发床
    //床类
    class Bed:virtual public Funiture
    {
    public:
      void sleep(){
          cout<<"在床上睡觉..."<<endl;
       }
    };
    
    //沙发
    class Sofa:virtual public Funiture
    {
    public :
      void sit(){
         cout<<"在沙发上睡觉..."<<endl;
        }
    };
    
    
    class SofaBed:public Bed,public Sofa
    {
    public:
       void sitAndSleep(){
           sit();//sofa
           sleep();//bed;
       }
    
    };
    
    
    int main(void){
    
     Bed b;
     b.sleep();
     cout<<"----------------------"<<endl;
    
    Sofa s;
    s.sit();
    cout<<"----------------------"<<endl;
    
    SofaBed sb;
    sb.sitAndSleep();
    
    sb.m;//报错!!!因为不知道用哪个父类的m,要用虚继承
    //改虚继承后不报错!
    
    return 0;
    }
    
    6.png 7.jpg

    相关文章

      网友评论

          本文标题:十五、继承和派生,兼容性原则

          本文链接:https://www.haomeiwen.com/subject/enynsftx.html