第54课 被遗弃的多重继承(下)

1、C++中的多重继承

    1.1、一个子类可以拥有 多个父类
    1.2、 子类拥有所有父类的 成员变量
    1.3、 子类继承所有父类的 成员函数
    1.4、 子类对象可以 当作任意父类对象 使用(父子类有赋值兼容性原则)
    1.5、 多重继承的 语法规则
class Derived: public BaseA, public BaseB, public BaseC {…};

2、多重继承问题一

    2.1、 通过多重继承得到的对象可以 拥有“不同的地址” !!!
    2.2、 解决方案:
    2.3、 原因分析(不同的父类指针指向子类中属于自己的那部分地址)
                    

/****************      多重继承问题一    *****************/
#include <iostream>
using namespace std;
class BaseA
{
    int ma;
public:
    BaseA(int a)
    {
        ma = a;
    }
    
    int getA()
    {
        return ma;
    }
};
class BaseB
{
    int mb;
public:
    BaseB(int b)
    {
        mb = b;
    }
    
    int getB()
    {
        return mb;
    }
};

//多继承
class Derived : public BaseA, public BaseB
{
    int mc;
public:
    Derived(int a, int b, int c):BaseA(a), BaseB(b)     //初始化列表。
    {
        mc = c;
    }
    
    int getC()
    {
        return mc;
    }
    
    void print()
    {
        cout << "ma = " << getA() << ", "
             << "mb = " << getB() << ", "
             << "mc = " << mc << endl;
    }
};
int main()
{
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;//12,每个类私有变量的字节数加在一起
    Derived d(1, 2, 3);
    
    d.print();
    
    cout << "d.getA() = " << d.getA() << endl;
    cout << "d.getB() = " << d.getB() << endl;//用类的对象访问成员方法
    cout << "d.getC() = " << d.getC() << endl;
    
    cout << endl;
    
    BaseA *pa = &d;
    BaseB *pb = &d;  //注意以上两行,都是将对象d的地址赋值给一个指针,表面上看似pa指针应该等于pb指针,但实际上不会这样,pa指向的是d对象中BaseA的子对象,而pb指向的是d对象中BaseB子对象的部分。(这两部分并未重合)
    cout << "pa = " << pa << endl;    //两个地址不相同
    cout << "pb = " << pb << endl;
    
    void* paa = pa;
    void* pbb = pb;
    
    if(paa == pbb)
    {
        cout << "point to the same Object" << endl;
    }
    else
    {
        cout << "ERROR" << endl;
    }      
    
    cout << "paa = " << paa << endl;    //两个地址不相同
    cout << "pbb = " << pbb << endl;
    
    return 0;
}

3、多重继承问题二:可能产生冗余的成员

            
    3.1、 当多重继承关系出现闭合时将产生 数据冗余 的问题!!!
    3.2、 解决方案: 虚继承
        3.2.1、 虚继承能够解决数据冗余问题
        3.2.2、 中间层 父类不用再关心顶层父类的初始化(但是中间层也要调用父类构造函数进行初始化。)
        3.2.3、 最终 子类必须直接调用顶层父类(以及中间父类) 的构造函数
    3.3、存在问题:当架构设计中需要继承时, 无法确定使用直接继承还是虚继承 !!

/*******************   多重继承问题二   ***************/
#include <iostream>
#include <string>
using namespace std;
class People
{
    string mName;
    int mAge;
public:
    People(string name, int age)//构造函数
    {
        mName = name;
        mAge = age;
    }
    
    void print()
    {
        cout << "Name = " << mName << ", "
             << "Age = " << mAge << endl;
    }
};
//先看看虚继承,然后再撤销虚继承。。中间类采用虚继承
class Teacher : virtual public People
{
public:
    Teacher(string name, int age): People(name, age)    //构造函数,初始化列表
    {
        
    }
};
//中间类采用虚继承
class Student : virtual public People
{
    public:
        Student(string name, int age): People(name, age)
        {
        
        }
        
};
//博士类(一个博士可能即是老师,又是学生),采用直接继承,如果中间层的Teacher和Student不采用虚继承的话,那在Doctor类中将有来自People类的mName,mAge等成员变量各两份,出现数据冗余现象,而且在子类Doctor中,如果直接mName = 1, 会出现二义性的错误,因为编译器不知道mName到底是来自Teacher的还是Student类的。
class Doctor : public Teacher, public Student
{
public:
    //请注意,构造函数中的最后一个初始化People,也就是说采用虚继承的话,则最终的子类仍需调用顶层父类的构造函数,这也是虚继承的一大问题,因为在实际开发中,有时很难确定最顶层的父类。
    Doctor(string name ,int age):Teacher(name, age), Student(name, age), People(name, age)
    {
    
    }
};
//各自私有属性是独立的
int main()
{
    Doctor d("SantaClaus", 25);//先父类,在朋友,再自己
    d.print();
    
    return 0;
}


4、多重继承可能产生多个虚函数表、


/**********************      多重继承问题三     *************/
#include <iostream>
using namespace std;
class BaseA
{
public:
    virtual void funcA()
    {
        cout << "BaseA::funcA()" << endl;
    }
};
class BaseB
{
public:
    virtual void funcB()
    {
        cout << "BaseB::funcB()" << endl;
    }
};
class Derived: public BaseA, public BaseB
{
public:
    Derived()
    {
    
    }
};
int main()
{
    Derived d;
    BaseA* pa = &d;
    BaseB* pb = &d;     //虽然都是将d地址赋给一个指针,但是,因为二者类不同,所以地址不同。
        //暴力强转会出现奇怪的现象。
    BaseB* ppb = (BaseB*)pa;  //暴力强转,BaseB*指针的原意是期望指向d对象中的BaseB子对象部分。    //但是暴力的强制转化会导致ppb指向对象的BaseA子对象,而不是期望的那样。
    BaseB* pbb = dynamic_cast<BaseB*>(pa);//使用dynamic_cast关键字来转换,编译器会先从d的地址找到这个对象,然后从其继承树上找到该d对象中BaseB子对象的部分,从而能正确的进行指针间的转换。(这是编译器自动为我们做的)
    //dynamic_cast关键字的使用;需要有继承关系,只能转换指针和引用,且需要虚函数的支持。
    
    cout << "sizeof(d) = " << sizeof(d) << endl;//因为子类继承两个父类,有两个虚函数表指针!!!!。
    cout << "Using pa to call funcA()...." << endl;
    pa->funcA();//父类的指针
    
    cout << "Using pb to call funcB()...." << endl;
    pb->funcB();//父类的指针
    
//因为pa这个地址实质上是指向BaseA,调用被virtual修饰的函数时,内部有一个vptr虚函数表指针具体指向的函数,会确定具体是哪个对象的函数。
    cout << "Using ppb to call funcB()...." << endl;
    ppb->funcB();   //期望调用BaseB::funcB,实际上调用的是BaseA::funcA!
                    //因为暴力强转使ppb指向了BaseA的对象,多态的缘故
    cout << "Using pbb to call funcB()...." << endl;
    pbb->funcB();//实际与期望一样。
    
    cout << "pa = "  << pa  << endl;    //BaseA*
    cout << "pb = "  << pb  << endl;    //BaseB*
    cout << "ppb = " << ppb << endl;    //BaseA*  暴力转换导致
    cout << "pbb = " << pbb << endl;    //BaseB*
    
    return 0;   
}


    4.1、 当类中存在虚函数表时,如果需要进行强制类型转换时,C++中推荐使用新式类型转换关键字!!!
    4.2、 解决方案:dynamic_cast
        //实际上暴力强转后还指向BaseA。具体的地址指向没有任何变化。
            

5、正确的使用多重继承

    5.1、 工程开发中的“多重继承”方式: 单继承+多接口
    5.2、 一些有用的工程建议:
        5.2.1、 先继承自一个父类,然后实现多个接口
        5.2.2、 父类提供 equal() 成员函数,用于判断指针是否指向当前对象
        5.2.3、 与多重继承相关的强制类型转换用 dynamic_cast(指针和引用) 完成

/*************          正确的多重继承方式       ***********/
#include <iostream>
using namespace std;
class Base
{
protected:
    int mi;
public:
    Base(int i)
    {
        cout << "父类被调用了" << endl; //父类
        mi = i;
    }
    
    int getI()
    {
        return mi;
    }
    
    //以下是个技巧,用来判断指针是否指向同一个对象,只要有对象调用函数,则会隐式的传入对象的指针this。
    bool equal(Base* obj)   //类的指针
    {
        return (this == obj);
    }
};
//接口1    :无成员变量,不能用来创建对象,只能被实现。
class Interface1
{
    public:
        virtual void add(int i) = 0;
        virtual void minus(int i) = 0;
};
class Interface2
{
public:
    virtual void multiply(int i) = 0;
    virtual void divide(int i) = 0; //纯虚函数
    
};
//单继承+多接口
class Derived : public Base, public Interface1, public Interface2
{
public:
    Derived(int i): Base(i) //初始化参数列表,构造函数,父类的初始化在子类的初始化列表中。
    {
    
    }
    
    void add(int i)
    {
        mi += i;
    }
    
    void minus(int i)
    {
        mi -= i;
    }
    
    void multiply(int i)
    {
        mi *= i;
    }
    
    void divide(int i)
    {
        if(i != 0)
        {
            mi /= i;
        }
    }
};
int main()
{  
    Base* o;
    Derived d(100);
    Derived* p = &d;
    Interface1* pInt1 = &d;
    Interface2* pInt2 = &d;
    
    cout << "p->getI() = " << p->getI() << endl;   //返回基类base的mi成员
    
    pInt1->add(10);         // i == 110
    pInt2->divide(11);      // i = 110 / 11
    pInt1->minus(5);        // i = 10 - 5
    pInt2->multiply(8);     //可以连续操作说明这两个接口指向同一个对象。但实际的地址值不同
    
    cout << "p->getI() = " << p->getI() << endl;   //40
    
    cout << endl;
    
    cout << "&d = " << &d << endl;
    cout << "p = " << p << endl;
    cout << "pInt1 = " << pInt1 << endl;
    cout << "pInt2 =  " << pInt2 << endl;
    
    cout << endl;
    //p->equal(pInt1);  //类型不匹配,这是接口类型。实际的是Base基类类型
    o = p;        //子类对象可以隐式转换为父类,赋值兼容性原则。    

    int a = p->equal(dynamic_cast<Base*>(pInt1));//动态的将这个地址指向的对象中Base那部分转换出来。
    int b = p->equal(dynamic_cast<Base*>(pInt2));
    
    cout << "pInt1 == p: "<< a << endl; //说明接口类的地址,与子类的地址相等。
    cout << "pInt2 == p: "<< b << endl;
    
    return 0;
}

6、小结

    6.1、 C++支持 多重继承 的编程方式
    6.2、 多重继承容易带来问题,如 “同一个对象的地址不同” “数据冗余” 问题等。
    6.3、 多继承中可能出现 多个虚函数表指针
    6.4、 多重继承相关的强制类型转换 dynamic_cast 完成
    6.5、 工程开发中采用 单继承+多接口 的方式使用多继承
    6.6、 父类提供成员函数 用于判断指针是否指向当前对象




































































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值