多个直接基类中产生派生类的能力,多重继承的派生类继承了所有父类的属性
18.3.1多重继承派生类的派生列表中可以包含多个基类,但这些类不能是final的
class ZooAnimal {};
class Endangered{};
class Bear:public ZooAnimal{};
class Panda:public Bear,public Endangered{};
多重继承的派生类从每个基类中继承状态
Pandda对象的概念结构
{
| { ZooAnimal成员
| Bear| Bear成员
| 子部分{
|
|
Panda |
| {Endangered成员
| Endangered|
| 子部分 {Panda成员
{
派生类构造函数初始化所有基类
//显示地初始化所有基类
Panda::Panda(std::string name,bool onExhibit):Bear(naem,onExhibit,"Panda"),Endangered(Endangered::critical){}
//隐式地使用Bear的默认构造函数初始化Bear子对象
Panda::Panda():Endangered(Endangered::critical){}
Panda对象按照下面的次序进行初始化
初始化ZooAnimal
初始化Panda的第一个基类Bear
初始化Panda的第二个基类Endangered;
初始化Panda
析构则相反
Panda->Endangered->Bear->XooAnimal
继承的构造函数与多重继承
struct Base1
{
Base1() = default;
Base1(const std::string& i) {
std::cout << "Base1 const string i" << std::endl;
}
Base1(std::shared_ptrp)
{
std::cout << "Base1 shared_ptr p" << std::endl;
}
};
struct Base2
{
Base2() = default;
Base2(const std::string& i)
{
std::cout << "Base2 const string i" << std::endl;
}
Base2(int p)
{
std::cout << "Base2 int p" << std::endl;
}
};
//报错D1 试图从两个基类中都继承D1::D1(const string&)
struct D1 :public Base1, public Base2
{
using Base1::Base1;
using Base2::Base2; //形参列表完全相同,程序报错
};
可以让上面的代码不用报错的方法就是定义自己的构造函数版本
struct D2 :public Base1,public Base2
{
using Base1::Base1; //从Base1继承构造函数
using Base2::Base2; //从Base2继承构造函数
//D2必须自定义一个接受string的构造函数
D2(const std::string &s):Base1(s),Base2(s){}
D2() = default; //一旦D2定义了它自己的构造函数。必须出现
};
多重继承的派生类的拷贝与移动操作
假设Panda使用了合成版本的成员 ling_ling的初始化过程
Panda ying_yang("ying_yang")
Panda ling_ling =ying_yang; //使用拷贝构造函数
ZonAnimal拷贝构造函数->ling_ling的Berar部分构造完成->Endangered拷贝构造函数创建对象相应的部分->Panda的拷贝构造函数
合成的移动构造函数的工作机理差不多一样
合成的拷贝赋值运算符的行为的移动赋值运算符的行为跟拷贝构造函数也是类似的
18.3.1节练习 解释下列声明的含义,有错误指出来
(a)class CADVehicle:public CAD,Vehicle{…};
Vehicle没有写public,默认CADVehicle private方式继承Venhicle的属性
(b)class DBList: public Lise,public Lise{…};
不能重复继承
©class iostream:public istream,public ostream{…};
继承了istream,ostream所有属性
18.22
class A {public: A() { cout << "A" << endl; } };
class B :public A { public: B() { cout << "B" << endl; } };
class C:public B { public: C() { cout << "C" << endl; } };
class X{public:X() { cout << "X" << endl; }};
class Y{ public:Y() { cout << "Y" << endl; } };
class Z:public X,public Y{ public:Z() { cout << "Z" << endl; } };
class MI:public C,public Z{ public:MI() { cout << "MI" << endl; } };
int main()
{
//A-B-C-X-Y-Z-MI
MI a;
}
18.3.2类型转换与多个基类
//接受Panda的基类引用的一系列操作
void print(const Bera&);
void highlight(const Endangered&);
ostream& operatpr<<(ostream&,const ZooAnimal&);
Panda ying_yang("ying_yang");
print(ying_yang); //把一个Panda对象传递给一个Bear的引用
highlight(ying_yang); //把一个Panda对象传递给一个Endangered的引用
cout<
对编译器来说转换到任意一种基类都一样好
void print(const Bear&);
void print(const Endangered&);
Panda ying_yang("ying_yang");
print(ying_yang); //二义性错误,得带前缀限定符才能区分开来
基于指针类型或引用类型的查找
对象,指针和引用的静态类型决定了我们能够使用那些成员,
Bear *pb =new Panda("ying_yang");
pb->print(); //正确 : Panda::print();
pb->cuddle();//错误 不属于Bear的接口
pb->highlight();//错误 不属于Bear的接口
delete pb; //正确: Panda::~Panda()
Endangered *pe =new Panda("ying_yang");
pe->print(); //正确: Panda::print()
pe->toes(); //错误:不属于Endangered的接口
pe->cuddle(); //错误: 不属于Endangered的接口
pe->highlight(); //正确: Panda::highlight()
delete pe; //正确: Panda::~Panda()
18.3.2节练习 https://zhuanlan.zhihu.com/p/362702706
18.23:使用练习18.22的继承体系以及下面定义的类D,同时假定每个类都定义了默认构造函数,请问下面那些类型转换是允许的
class D:public X,pulic C{...};
D*pd =new D;
(a)X*px =pd; (b)A*pa =pd;
(c)B*pb =pd; (d)C*pc =pd;
都是可以的
练习18.24:在第714页,我们使用一个指向Panda对象的Bear指针进行了一系列调用,假设我们使用的是一个指向Panda对象的ZooAnimal指针将发生什么情况,请对这些调用语句逐一进行说明。
ZooAnimal* p = new Panda("ying_yang");
p->print();
//p->cuddle(); 不属于ZooAnimal的接口
//p->highlight();不属于ZooAnimal的接口
delete p;
练习18.25假设我们有两个基类Base1和Base2,它们各自定义了一个名为print的虚成员和虚析构函数。从这两个基类中我们派生出下面的类,它们都重新定义了print函数:
class D1:public Base1{};
class D2:public Base2{};
class MI: public D1,public D2{};
Base*pb1 =new MI;
Base*pb2 =new MI;
D1*pd1 =new MI;
D2*pd2 =new MI;
(a)pb1->print() (b)pd1->print() (c)pd2->print() (d)delete pb2; (e)delele pd1; (f)delete pd2;
(a) (d)、(b)、和©均通过基类指针调用虚函数print,这些基类指针当前都指向MI类对象,所以均调用MI::print(); (a) (d)、(b)、和©均通过基类指针删除对象,这些基类指针当前都指向MI类对象,所以均通过虚机制调用MI析构函数,随着MI析构函数的执行,依次调用D2、Base2、D1和Base1的析构函数。
18.3.3多重继承下的类作用域
当一个类拥有多个基类时,有可能派生类从两个或更多基类中继承了同名成员的情况,不加前缀限定符直接使用该名字将引发二义性
//ZooAnimal 和Endangered都定义了名为max_weight的成员 ,并且Panda没有定义该成员
double d =ying_yang.max_weight(); //产生二义性错误
//在派生类中为该函数定义一个新版本
double Panda::max_weight()const
{
return std::max(ZooAnimal::max_weight(),Endangered::max_weight());
}
18.3.3节练习https://zhuanlan.zhihu.com/p/362717933
struct Base1
{
//struct默认情况下是公有的
void print(int i)const
{
cout << "Base1" << endl;
}
protected:
int ival;
double dval;
char cval;
private:
int* id;
};
struct Base2
{
void print(double)const
{
cout << "Base2" << endl;
}
protected:
double fval;
private:
double dval;
};
struct Derived :public Base1
{
void print(std::string)const
{
cout << "Derived" << endl;
}
protected:
std::string sval;
double dval;
};
struct MI :public Derived, public Base2
{
void print(vector)
{
cout << "MI" << endl;
}
protected:
int* ival;
vectordvec;
};
练习18.26已知如上所示的继承体系,下面对print的调用为什么是错误的?适当修改MI,令其对print的调用可以编译通过并正确执行。
int main()
{
MI mi;
mi.print(42);
}
mi.print(42):通过MI对象调用print函数,编译器通过了名字查找,确定调用的是mI类中定义的print函数,可mi类中print函数需要的是vector的形参,改为int就行了
练习18.27:已知如上所示的继承体系,同时假定为MI添加一个名为foo的函数
int ival;
double dval;
void MI::foo(double cval)
{
int dval;
//练习中的问题发生在此处
}
(a) 列出MI::foo中可见的所有名字
(b) 是否存在某个可见的名字是继承自多个基类的?
(c) 将Base1的dval成员与Derived的dval成员求和后赋给dval的局部实例。
(d) 将MI::dvec的最后一个元素的值赋给Base2::fval。
(e) 将从Base1继承的cval赋给从Derived继承的sval的第一个字符。
答:(a) ival, dval, cval, sval, fval, dvec, print
(b) dval, print
(c) dval = Base1::dval + Derived::dval;
(d) bas2::fval = devc.back();
(e) sval[0]=cval;
18.3.4虚继承
已io库的istream和ostream来举例,他们继承了一个base_ios的抽象基类,iostream是另一个类,它继承isteam和ostream
通过虚继承的机制解决了base_ios两次拷贝,承若共享它的基类,共享的基类子对象称为虚基类,
不论虚基类在继承体系中出现了多少次,在派生类中都只包含唯一一个共享的虚基类子对象
另一个Panda类
虚派生只影响从指定了虚基类的派生类中进一步派生出的类,它不会影响到派生类本身
使用虚基类
class ZooAnimal{};
//virtual 位置随意,表明后续的派生类共享虚基类的同一份实例,
class Raccon:public virtual ZooAnimal{};
class Bear:virtual public ZooAnimal{};
class Endangered{};
//Panda使用虚继承方式继承Raccon和Bear,Panda只有一个ZooAnima基类部分
class Panda:public Bear,public Raccon,public Endangered{};
支持向基类的常规类型转换
void dance(const Bear&);
void rummage(const Raccon&);
ostream& operator<<(ostream&, const ZooAnimal&);
int main()
{
Panda ying_yang;
dance(ying_yang); //把一个Panda对象当成Bear传递
rummage(ying_yang);//把一个Panda对象当Raccon传递
cout << ying_yang;//把一个Panda对象当成ZooAniaml传递
}
虚基类成员的可见性
class B {
public:
int x;
};
class D1 :virtual public B { };
class D2 :virtual public B { };
class D:public D1,public D2{};
D X;
X.x;//能通过D的对象使用x
class B {
public:
int x;
};
class D1 :virtual public B { int x; };
class D2 :virtual public B { };
class D:public D1,public D2{};
D X;
X.x;//能通过D的对象使用x,x的成员是D1的,派生类的x比共享基类的优先级高
class B {
public:
int x;
};
class D1 :virtual public B { int x; };
class D2 :virtual public B { int x; };
class D:public D1,public D2{};
D X;
X.x;//二义性错误,解决的方式是在派生类中卫成员自定义新的实例
18.3.5构造函数与虚继承
虚基类由最底层的派生类初始化
//Bear或Raccoon位于派生的最底层,构造函数初始化其ZooAnimal基类部分
Bear::Bear(std::string name,bool onExhibit):ZooAnimal(name,onExibiy,"Bear"){}
Raccoon::Raccoon(std::string name,bool onExhibit):ZooAniaml(name,onExhibit,"Raccoon"){}
//Panda位于派生的最底层并由它赋值初始化共享的ZooAnimal基类部分
Panda::Panda
(std::string name ,bool onExhibit):ZooAnimal(name,onExhibit,"Panda"),Bear(name,onExhibit),Raccoon(name,obExhibit)
,Endangered(Endangered::critical),sleeping_flag(false){}
虚继承的对象的构造方式
虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关
构造函数与析构函数的次序
class Chararctar{};
class BookCharacter:public Character{};
class ToyAnimal{};
class TeddyBear:public BookCharacter,public Bear,virtual ToyAnimal{}; //构造方式从左至右,最先开始找的是虚基类
构造一个TeddyBear对象依次调用以下程序
ZooAnimal(); //Bear的虚基类
ToAnimal(); //直接虚基类
Chararctar(); //第一个非虚基类的间接基类
BookCharacter(); //第一个直接非虚基类
Bear(); //第二个直接非虚基类
TeddyBear(); //最底层的派生类
合成的拷贝和移动构造函数按照完全相同的顺序执行,合成的赋值运算符一样
析构相反
18.3.5节练习 https://zhuanlan.zhihu.com/p/362730394
class Class {public: Class() { cout << "Class" << endl; } };
class Base :public Class { public:Base() { cout << "Base"< public: D1() { cout << "D1" << endl; } };
class D2:virtual public Base{ public: D2() { cout << "D2" << endl; } };
class MI:public D1,public D2{ public: MI() { cout << "MI" << endl; } };
class Final:public MI,public Class{ public: Final() { cout << "Final" << endl; } };
创建一个Final对象,构造函数:Class(),Base(),D1(),D2(),MI(),Class(),Final()
析构函数:~Final(), ~Class(), ~MI(), ~ D2(), ~D1(), ~ Base(), ~Class()
一个Base 子对象,两个Class子对象
Base* pb;
Class* pc;
MI* pmi;
D2* pd2;
pb =new Class; // 报错,不能将派生类指针指向基类对象
pc = new Final;//Class是final的一个二义性基类
pmi = pb; //不能将指向基类的指针对指向派生类的指针进行赋值
pd2 = pmi;
练习18.30: 在Base中定义一个默认构造函数, 一个拷贝构造函数和一个接受int形参的构造函数.在每个派生类中分别定义这三种构造函数,每个构造函数应该使用它的实参初始化其Base部分.
class Class {public: Class() { cout << "Class" << endl; } };
class Base :public Class
{
public:
Base():ival(0) { cout << "Base"<}
Base(const Base & b):ival(b.ival){}
protected:
int ival;
};
class D1 :virtual public Base
{ public: D1():Base(0) { cout << "D1" << endl; }
D1(int i):Base(1){}
D1(const D1 &d):Base(d){}
};
class D2:virtual public Base
{ public: D2() :Base(0) { cout << "D2" << endl; }
D2(int i) :Base(1) {}
D2(const D2& d) :Base(d) {}
};
class MI:public D1,public D2
{ public: MI() :Base(0) { cout << "MI" << endl; }
MI(int i) :Base(i) {}
MI(const MI& d) :Base(d),D1(d),D2(d){}
};
class Final:public MI,public Class
{ public: Final() :Base(0) { cout << "MI" << endl; }
Final(int i) :Base(i),MI(i) {}
Final(const MI& d) :Base(d),MI(d) {}
};



