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、
父类提供成员函数
用于判断指针是否指向当前对象