二、继承

作者: __bba3 | 来源:发表于2020-05-09 14:43 被阅读0次

    (1)继承

    <1>语法
    • 原则 :
      父类/子类 基类/派生类
      是否可以是继承:【子类 is a 父类】
    • 语法
    class 派生类:访问限定符 基类{
            结构体
    };
    

    如果不写访问限定符,默认是private继承。

    (2)访问权限

    • 1.子类继承了父类所有的成元变量和成员函数。与访问限定符无关。访问限定符只是限制了访问。
    • 2.子类内部要想访问父类的private成员,可以把private改为public或者protected(常用)
    <1>子类内部的访问权限
    • 1.无论那种继承方式,子类内部可以访问除了private的所有成员。
    • 2.子类继承父类,成员访问权限的变化:
    继承方式\父类成员 public protected private
    public继承 public protected 不可见
    protected继承 protected protected 不可见
    private继承 private private 不可见

    子类内部访问父类成员,只能访问publicprotected成员。

    <2>子类对象的访问权限
    • 子类对象只能访问其父类的public成员,不能访问其他成员。

    注意:
    (1)子类只有public继承父类的时候,才能访问父类的public成员,其他都不能访问。通常子类使用public继承父类。
    (2)public继承,可以访问,不能直接修改基类里的private成员,可以在父类中用接口来修改其私有变量的值。

    (3)继承关系的构造顺序

    <1>派生类的构造顺序
    • 派生类的构造顺序:父类---->子类成员变量------>子类
    • 派生类的析构顺序:和构造顺序相反。

    注意:没有默认构造函数的基类在派生类的初始化,必须在初始化列表中初始化。比如在三角形实例中。

    class Member{
    public:
        Member(int i){ cout << __func__ << endl;}
        ~Member(){ cout << __func__ << endl;}
    };
    class Father{
    public:
        Father(int i){ cout << __func__ << endl;}
        ~Father(){ cout << __func__ << endl;}
    };
    class Son:public Father{
        Member m;
    public:
        //没有默认构造函数的父类在子类中初始化,必须使用初始化列表的方式
        Son():m(1),Father(1){ cout << __func__ << endl;}
        ~Son(){ cout << __func__ << endl;}
    };
    int main(){
        Son s;
    }
    结果:
    Father
    Member
    Son
    ~Son
    ~Member
    ~Father
    

    (4)同名隐藏规则

    <1>概念

    如果子类与父类成员函数同名,无论参数是否相同,子类会隐藏父类的成员函数。

    <2>访问同名隐藏的父类
    • 1.在子类中添加【using 父类名::同名函数名;】,只能解决同名但不同参的情况;
    • 2.使用【子类对象.父类名::父类函数名;】:可以解决任何情况。
    class Father{
    public:
        void Test(){
            cout << "Father::Test()" << endl;
        }
        void Test(int i){
            cout << "Father::Test(" <<i <<")" <<endl;
        }
    };
    class Son:public Father{
    public:
        using Father::Test;//(1)只能解决参数不同的同名隐藏
        void Test(){
            cout << "Son::Test()" << endl;
        }
    };
    int main(){
        //不采用任何情况
        Son s;
        s.Test();//访问的是子类的成员函数
        s.Test(3)//会报错!
        //如果要调用父类的方法:
    //方法1:可以完全解决同名隐藏
        s.Father::Test(2);
        s.Father::Test();   
    //法(2):使用using 。。。,只能解决同名不同参的问题
        s.Test(3);
        //s.Test();//不能调用父类的Test()  
    }
    
    <3>对象查找函数的规则

    对象首先在子类中查找,如果没有在子类找到,会到父类中查找;如果子类中找到了同名的函数,并且参数相同就会执行该函数,如果参数不同就会报错,并不会再往父类中查找了。
    注意:指针访问和.访问的规则一样。

    (5)赋值兼容

    <1>前提

    必须是公有继承,才能谈赋值兼容。

    <2>概念
    • 在任何需要基类对象的地方都可以使用公有的派生类对象来代替。反之,不可。
    • 对象切割(Object Slicing):在赋值时舍弃派生类自己的成员,只进行基类数据成员的赋值。
    • 赋值兼容可能会产生对象切割的代价。
    <3>赋值兼容有三种情况:
    • 1. 派生类的对象可以赋值给基类对象
      会生成一个新的父类对象。
    Base base;
    Derive derive;
    base = derive;
    
    • 注意:
      (1) 子类特有的成员变量不能被父类访问。
      (2)子类给父类赋值以后,父类的大小是其本身成员变量的大小,子类的大小是继承于父类的大小加上子类本身的大小。
      (3)子类对象是可以初始化父类对象。
    • 例子:
    class Base{
    public:
        int  n;
    };
    class Derive:public Base{
    public:
        int m;
    };
    void Func(Base b){}
    Base Func(){
          Derive d;
          return d;
    }
    int main(){
        Base b;
        Derive d;
        d.n=10;
        d.m=100;
        b=d;//调用Base的赋值运算符重载
        //d=b;//错误,不可以这样赋值
        cout << b.n<<endl;//4
        //cout << b.m<<endl;//Base对象不能访问到子类的任何成员(对象切割)
        cout << sizeof(b)<<endl;//4
        cout << sizeof(d)<<endl;//8
        Base b2(d);//子类对象初始化父类对象(调用Base的拷贝构造函数)
    子类对象给父类对象赋值的应用:
    //1.函数参数对象传值  
        Func(d);//调用Base的拷贝构造函数
    //2.函数返回对象
        Func();////调用Base的拷贝构造函数
    
    • 2. 派生类的对象可以初始化基类的引用
      没有生成新的对象,只是起了一个别名。
    Derive d;
    Base& fb = d;//子类对象初始化父类的引用,类型决定大小和能否访问的成员
    1.d的地址和fb的地址是一样的!
    cout << &d <<endl;
    cout << &fb <<endl;
    2.初始化父类的对象是不能访问子类成员的。
    cout << sizeof(d) <<endl;//8
    cout << sizeof(fb)<<endl;//4
    d.Test();//子类对象可以访问子类的成员
    fb.Test();//父类引用虽然指向子类对象,但是不能访问子类成员,只能访问父类的成员
    3. 应用:
    void Func(Base& b){}//定义
    Func(d)//调用.(这里是引用不会调用拷贝构造函数,值传才会调用拷贝构造)
    
    • 3. 派生类的对象地址可以初始化基类的指针
      没有生成一个新的对象,只是父类指针指向父类特有的那一部分。
    Base* pb=&d;
    1.d的地址和fb的地址是一样的!
    cout << pb <<endl;
    cout << &d <<endl;
    2.初始化父类的对象是不能访问子类成员的。
    pb->Test();//只能访问父类特有的成员 
    pb->m;
    3. 应用;
    void Func(Base* b){}//定义
    Func(&d);//调用
    

    注意:
    1.父类引用子类对象与父类指针指向子类对象,都只能访问子类所继承的那部分成员
    2.即使父类和子类函数名和参数完全相同,也不能访问到子类的成员,要想访问与其同名的子类成员,就要实现多态

    (6)多重继承

    一个类可以同时继承多个父类的行为和特征功能。

    <1>格式
    class 类名 : public 基类1,public 基类2{};
    
    <2>大小

    多重继承的大小,等于继承的所有父类大小之和。

    <3>多重继承基类构造顺序

    父类在子类的继承顺序就是子类对象的初始化顺序,如果父类有继承先会初始化父类的继承。

    class Base{
    public:
        Base(){cout << __func__ <<endl;}
        ~Base(){cout << __func__ <<endl;}
        int n;
    };
    class A:public Base{
    public:
        A(){cout << __func__ <<endl;}
        ~A(){cout << __func__ <<endl;}
    };
    class B:public Base{
    public:
        B(){cout << __func__ <<endl;}
        ~B(){cout << __func__ <<endl;}
    };
    class C:public A,public B{
    public:
        C(){cout << __func__ <<endl;}
        ~C(){cout << __func__ <<endl;}
    }; 
    int main(){
         C c;
         cout << "Base size:" << sizeof(Base) << endl;//4
         cout << "A size:" << sizeof(A) << endl;//4
         cout << "B size:" << sizeof(B) << endl;//4
         cout << "C size:" << sizeof(C) << endl;//4+4=8
    执行结果:Base--->A--->Base--->B---->C---->~C---->~B---->~Base---->~A----->~Base
    }
    
    <4>多重继承的危害

    钻石继承/菱形继承。(有两套相同的成员函数和成员变量,造成成员冲突)

    (7)钻石继承/菱形继承

    <1>概念

    两个子类B,C继承同一个父类A,而又有子类D同时继承这两个子类B,C,继承关系画成一个图刚好是菱形。

    <2>产生的问题
    • (1)成员冲突:子类D有两套名字完全相同的成员变量和成员函数来自B和C。要想访问某个成员必须在其类D中指明是那个类的成员。
    • (2)多次构造和析构:见上面的多重继承。在构造父类时,由于父类都虚继承了祖父类,所以在构造是都会调用祖父类的构造函数,并且会多次释放。
    void Print()const{
    //成员变量
            cout << "a:" << EqualTriangle::a <<endl;//指明成员变量属于那个类。
            cout << "b:" << EqualTriangle::b <<endl;
            cout << "c:" << EqualTriangle::c <<endl;
            cout << "a:" << RightTriangle::a <<endl;
            cout << "b:" << RightTriangle::b <<endl;
            cout << "c:" << RightTriangle::c <<endl;
    //成员函数
             cout << er.EqualTriangle::Getlength() << endl;
          cout << er.RightTriangle::Getlength() << endl;
    

    说明:等腰直角三角形同时继承了等腰三角形和直角三角形,当调用Getlength时,不知道调用的是等腰三角形的还是直角三角形的。

    <3>虚继承
    • 解决成员冲突有两种方法:
      (1)在访问时加上类型(类名)来限制访问的是那个成员。
      (2)虚继承
    • 定义
      虚继承:在继承定义中包含了virtual关键字的继承关系。(A虚继承于Base)
      虚基类:在虚继承体系中的通过virtual继承而来的基类。(Base是A的虚基类)

    虚基类是一个相对概念,在虚继承关系中,父类相对与子类是虚基类

    • 格式
    class 类名:public virtual 基类{
    }
    
    • 注意:
      1.格式的写法
      2.关于孙子类的构造函数要加上前面父类和祖父类
      3.sizeof的变化
      4.孙子类对象成员函数的调用(调用的是父类的,不是祖父类的)
      5.初始化祖父类的次数的变化,之前是两次构造和析构,加上虚继承后只构造一次、析构一次。
    class EqualTriangle:public virtual Triangle{//添加虚继承
    class RightTriangle:public virtual Triangle{//添加虚继承
    class EqualRightTriangle:public EqualTriangle,public RightTriangle{
          EqualRightTriangle(floatright):EqualTriangle(sqrt(right*right*2),right),
    RightTriangle(right,right),Triangle(right,right,sqrt(right*right*2)){}//所有的构造函数都必须初始化,但是如果父类和祖父类是默认的构造函数,则不用写。 
    void Print()const{
            cout << "a:" << a <<endl;//虚继承后可以直接访问祖父类的成员变量
            cout << "b:" << b <<endl;
            cout << "c:" << c <<endl;
    }
    };
    int main(){
    cout << sizeof(EqualTriangle) <<endl;//12(3*4)  ---->24(8(一个指向虚继承的指针)+3*4+4(补齐))
    cout << sizeof(RightTriangle) << endl;//12  ---->24
    cout << sizeof(EqualRightTriangle) <<endl;//24(12+12)---->32(8*2(加了两个指针)+3*4+4(补齐))
    cout << er.GetArea() << endl;//调用的是父类(RightTriangle)函数,不是祖父类的。
    }
    

    虚继承的构造和析构顺序:

    class Base{
    public:
        Base(){cout<<__func__<<endl;}
        ~Base(){cout<<__func__<<endl;}
    };
    class A:public virtual Base{
    public:
        A(){cout<<__func__<<endl;}
        ~A(){cout<<__func__<<endl;}
    };
    class B:public virtual Base{
    public:
        B(){cout<<__func__<<endl;}
        ~B(){cout<<__func__<<endl;}
    };
    class C:public A,public B{ // 父类在子类中的继承顺序就是子对象中父类的构造顺序
    public:
        C(){cout<<__func__<<endl;}
        ~C(){cout<<__func__<<endl;}
    };
    class Emply{};
    int main(){
        C c;
        cout << sizeof(Base) << endl;
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
        cout << sizeof(C) << endl;
        // 空类
        cout << sizeof(Emply) << endl;
    }
    结果:
    Base
    A
    B
    C
    1
    8
    8
    16
    1
    ~C
    ~B
    ~A
    ~Base
    
    <4>关于多重继承总结

    (1)什么是多重继承?同时继承多个父类。
    (2)多重继承有什么危害?菱形继承/钻石继承。
    (3)什么是菱形继承/钻石继承?多重继承的两个或多个父类具有相同的祖先类。
    (4)菱形继承/钻石继承有什么危害?因为多重继承的两个或多个父类具有相同的祖先类。所以会有完全相同的属性和方法。因此当前多重继承类有两份相同的属性和方法。使用时会出现冲突。
    (5)如何解决菱形继承/钻石继承导致的冲突?使用虚继承
    (6)什么是虚继承?父类在继承具有相同的祖先类时,加上virtual.

    (8)对象构造顺序总结

    • 先父后子(继承)
    • 从左到右(多重继承)
    • 先虚后实(虚继承 )
    • 从上到下 (成员变量)
    • 由内及外 (组合)

    相关文章

      网友评论

        本文标题:二、继承

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