当前位置: 首页 > news >正文

大型 交友 网站 建设 公司/营销推广手段有什么

大型 交友 网站 建设 公司,营销推广手段有什么,云开发工程师,长沙创意网站建设多重继承的原理 多重继承(multiple inheritance)是指从多个直接基类中产生派生类的能力。 多重继承的派生类继承了所有父类的属性。 在面向对象的编程中,多重继承意味着一个类可以从多个父类继承属性和方法。 就像你有一杯混合果汁,它是由多种水果榨取…

多重继承的原理

多重继承(multiple inheritance)是指从多个直接基类中产生派生类的能力。

多重继承的派生类继承了所有父类的属性。

在面向对象的编程中,多重继承意味着一个类可以从多个父类继承属性和方法。

就像你有一杯混合果汁,它是由多种水果榨取而来的,每种水果都为这杯果汁带来了独特的味道和营养。

假设我们有三个类:Animal(动物)、Bird(鸟类)和Swimmer(游泳者)。

Animal类有eat()方法,Bird类有fly()方法,而Swimmer类有swim()方法。

现在,我们想要创建一个新的类叫做SeaBird(海鸟),它既能飞,又能游泳,还像其他动物一样吃东西。这时候,我们就可以使用多重继承来实现这个需求。


多重继承

多重继承的声明格式如下

class 派生类名:访问说明符 基类1名,访问说明符 基类名2...
{
}或者
struct 派生类名:访问说明符 基类1名,访问说明符 基类名2...
{
}

在派生类的派生列表中可以包含多个基类

class A1:public A{...};
class AB:public A,public B{...};

每个基类包含一个可选的访问说明符。一如往常,如果访问说明符被忽略掉了,则关键字class对应的默认访问说明符是private,关键字struct对应的是public。

class A1:A{};
//和class A1:private A{}等价
//和struct A2:private A{}等价struct A2:A{};
//和struct A2:public A{}等价
//和class A1:public A{}等价

和只有一个基类的继承一样,多重继承的派生列表也只能包含已经被定义过的类,而且这些类不能是final的。

对于派生类能够继承的基类个数,C++没有进行特殊规定;但是在某个给定的派生列表中,同一个基类只能出现一次。

class A1:A,A{}//这是错误的

多重继承的派生类从每个基类中继承状态

在多重继承关系中,派生类的对象包含有每个基类的子对象。

就是说一个派生类对象不仅包含自己特有的部分,还包含它的基类部分

我们举个例子

class A{
int a;
}
class A1:A{
int b;
}

一个A1对象不仅含有它特有的部分(变量b),还含有它的基类部分(变量a) 

派生类构造函数初始化所有基类 

构造一个派生类的对象将同时构造并初始化它的所有基类子对象。

与从一个基类进行的派生一样,多重继承的派生类的构造函数初始值也只能初始化它的直接基类

class A1
{public:A1(int a_):a(a_){}int a;
};
class A2
{
public:int b;A2(int b_):b(b_){}
};
class AA1 :A1,A2
{int c;AA1(int a_,int b_,int c_):A1(a_),A2(b_),c(c_){}//显式地初始化所有基类
};
class A1
{public:A1(int a_):a(a_){}A1(){}int a;
};
class A2
{
public:int b;A2(int b_):b(b_){}
};
class AA1 :A1,A2
{int c;AA1(int a_,int b_,int c_):A2(b_),c(c_){}//隐式的使用A1的默认构造函数来初始化a
};

 多重继承派生类的构造函数

多重继承派生类的构造函数形式与单一继承时的构造函数形式基本相同,只是在派生类的构造函数初始化列表中调用多个基类构造函数。

一般形式为:

派生类名(形式参数列表):基类名1(基类1构造函数实参列表),基类名2(基类2构造函数实参列表),...,成员对象名1(子对象1属类构造函数实参列表),...,派生类初始化列表
{派生类初始化函数体
}

派生类的构造函数初始值列表将实参分别传递给每个直接基类。

其中基类的构造顺序与派生列表中基类的出现顺序保持一致,而与派生类构造函数初始值列表中基类的顺序无关。


其调用顺序是:

  1. 调用基类构造函数,各个基类按定义时的次序先后调用;
  2. 调用成员对象构造函数,各个子对象按声明时的次序先后调用;
  3. 执行派生类初始化列表;
  4. 执行派生类初始化函数体;

例:多重继承举例

#include<iostream> 
using namespace std; class Base1 {private:int b1;public:Base1() {b1=0;cout<<"默认构造Base1:"<<"b1="<<b1<<endl;}Base1(int i) {b1=i;cout<<"构造Base1:"<<"b1="<<b1<<endl;}
};class Base2 {private:int b2;public:Base2() {b2=0;cout<<"默认构造Base2:"<<"b2="<<b2<<endl;}Base2(int j) {b2=j;cout<<"构造Base2:"<<"b2="<<b2<<endl;}
};class Base3 {public:Base3() {cout<<"默认构造Base3:"<<endl;}
};class Derive : public Base1,public Base2,public Base3 {private:Base1 memberBase1;Base2 memberBase2;Base3 memberBase3;public:Derive() {cout<<"默认构造函数Derive."<<endl; }Derive(int a,int b,int c,int d): Base1(a),Base2(b),memberBase1(c),memberBase2(d) {cout<<"构造Derive."<<endl;}
};int main()
{cout<<endl<<"创建派生类对象obj1:"<<endl;Derive obj1;cout<<endl<<"创建派生类对象(1,2,3,4):"<<endl;Derive obj2(1,2,3,4);return 0;
}



运行结果:

创建派生类对象obj1:
默认构造Base1:b1=0//基类默认构造函数,下面2个也是
默认构造Base2:b2=0
默认构造Base3:
默认构造Base1:b1=0//成员对象的构造函数,下面2个也是
默认构造Base2:b2=0
默认构造Base3:
默认构造函数Derive,//派生类默认构造函数创建派生类对象(1.2.3.4):
构造Base1:b1=1   //基类构造函数,下面2个也是
构造Base2:b2=2
默认构造Base3:
构造Base1:b1=3  //成员对象的构造函数,下面2个也是
构造Base2:b2=4
默认构造Base3:
构造Derive.     //派生类构造函数

继承的构造函数的易错点

在C++11新标准中,允许派生类从它的一个或几个基类中继承构造函数。

但是如果从多个基类中继承了相同的构造函数(即形参列表完全相同)则程序将产生错误:

struct Base1 {Base1() = default;Base1(const std::string&) {}Base1(std::shared_ptr<int>) {}
};
struct Base2 {Base2() = default;Base2(const std::string&) {}Base2(int) {}
};
//错误:D1试图从两个基类中都继承D1::Dl(const string&)
struct D1 : public Base1, public Base2 {using Base1::Base1; //从Base1继承构造函数using Base2::Base2; // 从Base2继承构造函数
};

发生错误的原因是D1企图多个基类中继承了相同的构造函数(即形参列表完全相同),而且它没有定义自己版本的构造函数。

有两种解决办法

第一种方法

如果我们把Base1的第二个构造函数的函数参数改为int,就不会有错误了

struct Base1 {Base1() = default;Base1 (int&) {}Base1(std::shared_ptr<int>) {}
};
struct Base2 {Base2() = default;Base2(const std::string&) {}Base2(int) {}
};
//正确
struct D1 : public Base1, public Base2 {using Base1::Base1; //从Base1继承构造函数using Base2::Base2; // 从Base2继承构造函数
};

第二种方法

如果一个类从它的多个基类中继承了相同的构造函数,则这个类必须为该构造函数定义它自己的版本:

struct Base1 {Base1() = default;Base1 (const std::string&) {}Base1(std::shared_ptr<int>) {}
};
struct Base2 {Base2() = default;Base2(const std::string&) {}Base2(int) {}
};
//正确
struct D1 : public Base1, public Base2 {using Base1::Base1; //从Base1继承构造函数using Base2::Base2; // 从Base2继承构造函数D1(const std::string&a):Base1(a),Base2(a){}
};

注意不能是不带任何参数的默认构造函数

struct Base1 {Base1() = default;Base1 (const std::string&) {}Base1(std::shared_ptr<int>) {}
};
struct Base2 {Base2() = default;Base2(const std::string&) {}Base2(int) {}
};
//正确
struct D1 : public Base1, public Base2 {using Base1::Base1; //从Base1继承构造函数using Base2::Base2; // 从Base2继承构造函数D1(){}//错误
};

析构函数与多重继承

和往常一样,派生类的析构函数只负责清除派生类本身分配的资源,派生类的成员及基类都是自动销毁的。合成的析构函数体为空。

析构函数的调用顺序正好与构造函数相反。

我们可以来验证一番

#include<iostream>
using namespace std;
class A1
{public:A1(int a_) :a(a_) { cout << "A1的构造函数被调用" << endl; }A1(){}int a;~A1() { cout << "A1的析构函数被调用" << endl; }
};class A2
{
public:int b;A2(int b_):b(b_) { cout << "A2的构造函数被调用" << endl; }~A2() { cout << "A2的析构函数被调用"<<endl; }
};
class AA1 :A1,A2
{
public:int c;AA1(int a_,int b_,int c_):A1(a_),A2(b_),c(c_) { cout << "AA1的构造函数被调用" << endl; }~AA1() { cout << "AA1的析构函数被调用" << endl; }
};
int main()
{{AA1 a(1, 2, 3);}
}

事实确实如此

多重继承的派生类的拷贝与移动操作

与只有一个基类的继承一样,多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符,则必须在完整的对象上执行拷贝、移动或赋值操作。

只有当派生类使用的是合成版本的拷贝、移动或赋值成员时,才会自动对其基类部分执行这些操作。在合成的拷贝控制成员中,每个基类分别使用自己的对应成员隐式地完成构造、赋值或销毁等工作。

例如

class Bear:public Zooanimal{}
class Panda:public Bear,public Endangered{}Panda ying_ yang("ying_yang");
panda ling_ling =ying_yang;// 使用拷贝构造函数


将调用Bear的拷贝构造函数,后者又在执行自己的迷贝任务之前先调用ZooAnimal的的拷贝构造函数。一旦ling_ling 的Bear 部分构造完成,接着就会调用 Endangered来创建对象相应的部分。最后,执行Panda的拷贝构造函数。合成的移动构造函数的工作机理与之类似。

合成的拷贝赋值运算符的行为与拷贝构造函数报相似,它首先球值Bear 部分(并且通过Bear赋值ZooAnimal部分),然后赋值Endanagered部分,最后是Panda部分。

类型转换与多个基类

在只有一个基类的情况下,公有继承派生类的指针或引用能自动转换成一个可访问基类的指针或引用。多个基类的情况与之类似。

我们可以令某个可访问基类的指针或引用直接指向一个派生类对象。

例如,一个ZooAnimal、Bear或Endangered类型的指针或引用可以绑定到Panda对象上://接受

Panda的基类引用的一系列操作
 

class Bear:public Zooanimal{}
class Panda:public Bear,public Endangered{}void print(const Bear&);
void highlight (const Endangered&) ;
ostream& operator<<(ostream&, const ZooAnimal&);Panda ying yang("ying_yang");
print(ying_yang); //把一个Panda对象传递给一个Bear的引用
highlight(ying_yang); // 把一个 Panda 对象传递给一个Endangered的引用
cout << ying yang << endl;// 把一个Panda 对象传递给一个ZooAnimal的引用

编译器不会在派生类向基类的几种转换中进行比较和选择,因为在它看来转换到任意一种基类都一样好。

例如,如果存在如下所示的print重载形式:

void print(const Bear&);
void print(const Endangered&);


则通过Panda对象对不带前缀限定符的print函数进行调用将产生编译错误:

Panda ying_yang("ying_yang");
print(ying_yang); // 二义性错误


基于指针类型或引用类型的查找

与只有一个基类的继承一样,对象、指针和引用的前态类里快定了我们能够使用哪些成员。

如果我们使用一个ZooAnimal指针,则只有定义在zooAnimal中的操作是可以使用的,Panda 接口中的 Bear、Panda和Endangered特有的部分都不可见。

类似的,一个 Bear 类型的指针或引用只能访问BearzooAnimal的成员,一个Endangered的指针或引用只能访问Endangered的成员

举个例子,已知我们的类已经定义了表中列出的虚函数,考虑下面的这些函数调用。

在ZooAnimal/Endangered中定义的虚函数
函数含有自定义版本的类
printZooAnimal::ZooAnimal
Bear::Bear
Endangered::Endangered
Panda::Panda
highlightEndangered::Endangered
Panda::Panda
toesBear::Bear
Panda::Panda
cuddlePanda::Panda
析构函数ZooAnimal::ZooAnimal
Endangered::Endangered

class Bear:public Zooanimal{}
class Panda:public Bear,public Endangered{}Bear *pb = new Panda("ying yang");
pb->print(); // 正确:Panda::print()
pb->cuddle(); // 错误:不属于Bear的接口
pb->highlight(); // 错误:不属于Bear的接口
delete pb; // 正确:Panda::~Panda()


当我们通过Endangered的指针或引用访问一个Panda 对象时,Panda 接口中Panda特有的部分以及属于Bear的部分都是不可见的:

class Bear:public Zooanimal{}
class Panda:public Bear,public Endangered{}Endangered *pe = new Panda("ying yang");
pe->print(); //正确:Panda::print()
pe->toes(); // 错误:不属于 Endangered的接口
pe->cuddle(); // 错误:不属于 Endangered的接口
pe->highlight(); // 正确:Panda::highlight()
delete pe; // 正确:Panda::~Panda()

a

二义性问题及名字支配规则

1.二义性问题

  1. 在只有一个基类的情况下,派生类的作用域嵌套在直接基类和间接基类的作用域中。查找过程沿着继承体系自底向上进行,直到找到所需的名字。派生类的名字将隐藏基类的同名成员。
  2. 在多重继承的情况下,相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到,则对该名字的使用将具有二义性。

当一个类拥有多个基类时,有可能出现派生类从两个或更多基类中继承了同名成员的情况。此时,不加前缀限定符直接使用该名字将引发二义性。

多重继承时,多个基类可能出现同名的成员。在派生类中如果使用一个表达式的含义能解释为可以访问多个基类的成员,则这种对基类成员的访问就是不确定的,称这种访问具有二义性。C++要求派生类对基类成员的访问必须是无二义性的

例如:

class A {public:void fun() { cout<<"a.fun"<<endl; }
};
class B {public:void fun() { cout<<"b.fun"<<endl; }void gun() { cout<<"b.gun"<<endl; }
};
class C:public A,public B {public:void gun() { cout<<"c.gun"<<endl; } //重写gun()void hun() { fun(); } //出现二义性
};
int main()
{C c,*p=&c;return 0;
}


使用成员名限定可以消除二义性,例如:

//成员名限定消除二义性

c.A::fun();
c.B::fun();
p->A::fun();
p->B::fun();



基本形式为:

对象名.基类名::成员名
对象指针名->基类名::成员名


2.名字支配规则

C++对于在不同的作用域声明的名字,可见性原则是:如果存在两个或多个具有包含关系的作用域,外层声明了一个名字,而内层没有再次声明相同的名字,那么外层名字在内层可见;如果在内层声明了相同的名字,则外层名字在内层不可见,这时称内层名字隐藏(或覆盖)了外层名字,这种现象称为隐藏规则

在类的派生层次结构中,基类的成员和派生类新增的成员都具有类作用域,二者的作用域是不同的:基类在外层,派生类在内层

如果派生类声明了一个和基类成员同名的新成员,派生的新成员就覆盖了基类同名成员,直接使用成员名只能访问到派生类的成员

如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数不同,从基类继承的同名函数的所有重载形式也都会被覆盖

如果要访问被覆盖的成员,就需要使用基类名和作用域限定运算符来限定

派生类D中的名字N覆盖基类B中同名的名字N,称为名字支配规则。如果一个名字支配另一个名字,则二者之间不存在二义性,当选择该名字时,使用支配者的名字,如:

c.gun(); //使用C::gun



如果要使用被支配者的名字,则应使用成员名限定,例如:

c.B::gun(); //使用B::gun



虚基类

C++引入虚基类的目的是为了解决多继承时可能出现的冲突问题。当一个类通过多个路径继承了同一个基类时,如果不使用虚基类,那么在派生类中就会存在多个基类子对象,这可能会导致数据重复和二义性的问题。

通过使用虚基类,可以确保在派生类中只有一个基类子对象,从而避免数据重复和二义性的问题。虚基类的成员在派生类中只有一个副本,不会重复出现。

另外,虚基类还可以实现多态性,能够让派生类对象按照基类的指针或引用进行使用,从而提高代码的灵活性和可扩展性。

1.虚基类的定义

虚基类是在派生类定义时,指定继承方式时声明的。声明虚基类的一般形式为:

class 派生类名: virtual 访问标签 虚基类名,... { //类体成员列表
};

还有一种形式是

class 派生类名: 访问标签 virtual 虚基类名,... { // 成员列表
};



需要注意,为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承(我们下面会举例子)

例:虚基类举例

#include<iostream>
using namespace std; class A { //声明为基类Apublic:A(int n) { //A类的构造函数 nv=n;cout<<"Member of A"<<endl;} void fun() {cout<<"fun of A"<<endl;}private:int nv;
};class B1: virtual public A { //声明A为虚基类 public:B1(int a):A(a) { //B1类的构造函数 cout<<"Member of B1"<<endl;}private:int nv1; 
};class B2: virtual public A { //声明A为虚基类public:B2(int a):A(a) { //B2类的构造函数cout<<"Member of B2"<<endl; } private:int nv2;
};class C: public B1,public B2 {public://派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用C(int a):A(a),B1(a),B2(a) {cout<<"Member of C"<<endl;}void fund() {cout<<"fun of C"<<endl;}private:int nvd;
};int main()
{C c1(1);c1.fund();c1.fun(); //不会产生二义性return 0; 
}

 现在C对象只将包含A对象的一个副本。从更本质的说,继承的B1和B2对象共享一个A对象,而不是各种引入自己的A对象副本。这样子调用A类方法就不会有二义性了(不知道调用B!继承的还是B2继承的A类方法)。

如果我们不引入虚基类,再看看这个例子

#include<iostream>
using namespace std;class A { //声明为基类A
public:A(int n) { //A类的构造函数 nv = n;cout << "Member of A" << endl;}void fun() {cout << "fun of A" << endl;}
private:int nv;
};class B1 : virtual public A { //声明A为虚基类 
public:B1(int a) :A(a) { //B1类的构造函数 cout << "Member of B1" << endl;}
private:int nv1;
};class B2 :  public A {         //注意这里没有声明A为虚基类
public:B2(int a) :A(a) { //B2类的构造函数cout << "Member of B2" << endl;}
private:int nv2;
};class C : public B1, public B2 {
public://派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用C(int a) :A(a), B1(a), B2(a) {cout << "Member of C" << endl;}void fund() {cout << "fun of C" << endl;}
private:int nvd;
};int main()
{C c1(1);c1.fund();c1.fun(); //产生了二义性return 0;
}

我们将B2后面的virtual去掉,使A类失去虚基类的性质。发现上面这个程序出现了二义性,编译器不知道调用B1继承的A类方法还是B2继承的A类方法。 


2.虚基类的初始化

如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生和间接派生)中,都要通过构造函数的初始化表对虚基类进行初始化。例如:

class A { public: A(int) { } }; //定义构造函数有参数的基类
class B: virtual public A {public:B(int a):A(a) { } //对基类A初始化
};
class C: virtual public A {public:C(int a):A(a) { } //对基类A初始化
};
class D: public B,public C {public:D(int a):A(a),B(a),C(a) { }
};


在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化

关于虚基类的说明:

(1)一个类可以在一个类族中即被用作虚基类,也被用作非虚基类

(2)派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的默认构造函数

(3)在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行多重继承应用举例

#include<iostream>
using namespace std; enum Color { //颜色枚举类型 Red,Yellow,Green,White
};class Circle { //圆类Circle的定义private:float radius;public:Circle(float r) {radius=r;cout<<"Circle initialized!"<<endl;} ~Circle() {cout<<"Circle destroyed!"<<endl;}float Area() {return 3.1415926*radius*radius;}
};class Table { //桌子类Table的定义private:float height;public:Table(float h) {height=h;cout<<"Table initialized!"<<endl;}~Table() {cout<<"Table destroyed!"<<endl;}float Height() {return height;}
};class RoundTable: public Table,public Circle { //圆桌类的定义private:Color color;public:RoundTable(float h,float r,Color c);int GetColor() {return color;}~RoundTable() {cout<<"RoundTable destroyed!"<<endl;}
};RoundTable::RoundTable(float h,float r,Color c):Table(h),Circle(r) { //圆桌构造函数的定义color=c;cout<<"RoundTable initialized!"<<endl; 
}int main()
{RoundTable cir_table(15.0,2.0,Yellow);cout<<"The table properties are:"<<endl;cout<<"Height="<<cir_table.Height()<<endl; //调用Table类的成员函数cout<<"Area="<<cir_table.Area()<<endl; //调用circle类的成员函数cout<<"Color="<<cir_table.GetColor()<<endl; //调用RoundTable 类的成员函数return 0;
}



运行结果:

Table initialized!
Circle initialized!
RoundTable initialized!
The table properties are:
Height=15
Area=12.5664
Color=1
RoundTable destroyed!
Circle destroyed!
Table destroyed!

支持向基类的常规类型转换 

不论基类是不是虚基类,派生类对象都能被可访问基类的指针或引用操作。

例如,下面这些从Panda向基类的类型转换都是合法的:

class Raccoon:public virtual ZooAnimal{}
class Bear:virtual public ZooAnimal{}
class Panda:public Bear,public Raccoon,public Endangered{}void dance (const Bear&);
void rummage (const Raccoon&);
ostream& operator<<(ostream&, const ZooAnimal&);Panda ying_yang; // 正确:把一个Panda对象当成Bear传递
dance(ying_yang);//正确:把一个Panda对象当成Raccoon传递
rummage (ying_yang);// 正确:把一个 Panda 对象当成 ZooAnimal 传递
cout << ying_yang; 


虚基类成员的可见性

因为在每个共享的虚基类中只有唯一一个共享的子对象,所以该基类的成员可以被直接访问,并且不会产生二义性。

此外,如果虚基类的成员只被一条派生路径覆盖,则我们仍然可以直接访问这个被覆盖的成员。但是如果成员被多于一个基类覆盖,则一般情况下派生类必须为该成员自定义一个新的版本。

假定类B定义了一个名为x的成员,D1和D2都是从B虚继承得到的,D继承了D1和D2,则在D的作用域中,x通过D的两个基类都是可见的。如果我们通过D的对象使用x,有三种可能性

如果在D1和D2中都没有x的定义,则x将被解析为B的成员,此时不存在二义性,一个D的对象只含有x的一个实例。

class B
{
public:int x;B(int x_):x(x_){}B() {}
};
class D1:virtual public B
{
public:D1(int a_):B(a_){}
};
class D2 :virtual public B
{
public:D2(int a_):B(a_) {}
};
class D :public D1, public D2
{public:D(int a1,int a2):B(a1),D1(a1),D2(a2){}
};
int main()
{D a(1, 1);cout << a.x << endl;//解析为B.x
}


如果x是B的成员,同时是D1和D2中某一个的成员,则同样没有二义性,派生类的x比共享虚基类B的x优先级更高。

class B
{
public:int x;B(int x_):x(x_){}B() {}
};
class D1:virtual public B
{
public:D1(int a_):B(a_){}
};
class D2 :virtual public B
{
public:D2(int a_):B(a_),x(a_) {}int x;//定义了x
};
class D :public D1, public D2
{public:D(int a1,int a2,int a3):B(a1),D1(a2),D2(a3){}
};
int main()
{D a(1, 2,3);cout << a.x << endl;//结果是3
}

如果在D1和D2中都有x的定义,则直接访问x将产生二义性问题。

class B
{
public:int x;B(int x_):x(x_){}B() {}
};
class D1:virtual public B
{
public:D1(int a_) :B(a_), x(a_) {}int x;//定义了x
};
class D2 :virtual public B
{
public:D2(int a_):B(a_),x(a_) {}int x;//定义了x
};
class D :public D1, public D2
{public:D(int a1,int a2,int a3):B(a1),D1(a2),D2(a3){}
};
int main()
{D a(1, 2,3);cout << a.x << endl;
}


与非虚的多重继承体系一样,解决这种二义性问题最好的方法是在派生类中为成员自定义
新的实例。


构造函数与虚继承

在虚派生中,虚基类是由最低层的派生类初始化的。

class Raccoon:public virtual ZooAnimal{}
class Bear:virtual public ZooAnimal{}
class Panda:public Bear,public Raccoon,public Endangered{}

以我们的程序为例,当创建Panda对象时,由Panda的构造函数独自控制zooAnimal的初始化过程。

为了理解这一规则,我们不妨假设当以普通规则处理初始化任务时会发生什么情况

在此例中,虚基类将会在多条继承路径上被重复初始化。

以ZooAnimal为例,如果应用普通规则,则Raccoon和Bear都会试图初始化Panda对象的ZooAnimal部分

当然,继承体系中的每个类都可能在某个时刻成为“最低层的派生类”。 

只要我们能创建虚基类的派生类对象,该派生类的构造函数就必须初始化它的虚基类。

例如在我们的继承体系中,当创建一个Bear(或Raccoon)的对象时,它已经位于派生的最低层,因
此Bear(或Raccoon)的构造函数将直接初始化其ZooAnimal基类部分;

class Raccoon:public virtual ZooAnimal{}
class Bear:virtual public ZooAnimal{}
class Panda:public Bear,public Raccoon,public Endangered{}Bear::Bear(std::string name, bool onExhibit):ZooAnimal (name, onExhibit, "Bear"){}Raccoon::Raccoon(std::string name, bool onExhibit):ZooAnimal (name, onExhibit, "Raccoon") {}


而当创建一个Panda 对象时,Panda位于派生的最低层并由它负责初始化共享的ZooAnimal基类部分。即使ZooAnimal不是Panda的直接基类,Panda的构造函数也可以初始化ZooAnimal:
 

class Raccoon:public virtual ZooAnimal{}
class Bear:virtual public ZooAnimal{}
class Panda:public Bear,public Raccoon,public Endangered{}Panda::Panda(std::string name, bool onExhibit):ZooAnimal (name, onExhibit, "Panda"),Bear (name, onExhibit),Raccoon(name, onExhibit) Endangered(Endangered::critical)sleeping_flag(false) {}

虚继承的对象的构造方式

含有虚基类的对象的构造顺序与一般的顺序稍有区别:

首先使用提供给最低层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生列表中出现的次序依次对其进行初始化。

例如,当我们创建一个Panda对象时:

  1. 首先使用Panda 的构造函数初始值列表中提供的初始值构造虚基类ZooAnimal部分。
  2. 接下来构造Bear部分。
  3. 然后构造Raccoon部分。
  4. 然后构造第三个直接基类Endangered。
  5. 最后构造Panda部分。

如果Panda 没有显式地初始化ZooAnimal基类,则ZooAnimal的默认构造函数将被调用。如果ZooAnimal没有默认构造函数,则代码将发生错误。

虚基类总是先于非虚基类构造,与它们在继承体系中的求序和位置无关。

构造函数与析构函数的次序


一个类可以有多个虚基类。此时,这些虚的子对象按照它们在派生列表中出现的顺序从左向右依次构造。

例如,在下面这个稍显杂乱的Teddvpear派生关系中有两个虚基类:

ToyAnimal是直接虚基类,ZooAnima1是Bear的虚基类:
 

class Character {};
class BookCharacter:public Character{};
class ToyAnimal {};
class TeddyBear : public BookCharacter, public Bear, public virtual ToyAnimal{};


编译器按照直接基类的声明顺序对其依次进行检查,以确定其中是否含有虚基类。

如果有,则先构造虚基类,然后按照声明的顺序逐一构造其他非虚基类。

因此,要想创建一个TeddyBear对象,需要按照如下次序调用这些构造函数:

ZooAnimal(); // Bear的虚基类
ToyAnimal(); //直接虚基类
Character(); //第一个非虚基类的间接基类
BookCharacter(); // 第一个直接非虚基类
Bear();// 第二个直接非虚基类
TeddyBear(); //最低层的派生类


合成的拷贝和移动构造函数按照完全相同的顺序执行,合成的赋值运算符中的成员也按照该顺序赋值。

和往常一样,对象的销毁顺序与构造顺序正好相反,首先销毁TeddyBear 部分,最后销毁zooAnimal部分。

相关文章:

C++多重继承与虚继承

多重继承的原理 多重继承(multiple inheritance)是指从多个直接基类中产生派生类的能力。 多重继承的派生类继承了所有父类的属性。 在面向对象的编程中&#xff0c;多重继承意味着一个类可以从多个父类继承属性和方法。 就像你有一杯混合果汁&#xff0c;它是由多种水果榨取…...

请简单介绍一下Shiro框架是什么?Shiro在Java安全领域的主要作用是什么?Shiro主要提供了哪些安全功能?

请简单介绍一下Shiro框架是什么&#xff1f; Shiro框架是一个强大且灵活的开源安全框架&#xff0c;为Java应用程序提供了全面的安全解决方案。它主要用于身份验证、授权、加密和会话管理等功能&#xff0c;可以轻松地集成到任何Java Web应用程序中&#xff0c;并提供了易于理解…...

TouchGFX之Button

TouchGFX中的按钮是一种感应触控事件的控件&#xff0c;能够在按钮被按下/释放时发送回调 代码 #ifndef TOUCHGFX_ABSTRACTBUTTON_HPP #define TOUCHGFX_ABSTRACTBUTTON_HPP #include <touchgfx/Callback.hpp> #include <touchgfx/events/ClickEvent.hpp> #includ…...

计算机组成原理 — 指令系统

指令系统 指令系统指令的概述指令的格式指令的字长取决于 操作数类型和操作种类操作数的类型数据在存储器中的存放方式操作类型 寻址方式指令寻址数据寻址立即寻址直接寻址隐含寻址间接寻址寄存器寻址寄存器间接寻址基址寻址变址寻址堆栈寻址 RISC 和 CISC 技术RISC 即精简指令…...

使用easyYapi生成文档

easyYapi生成文档 背景1.安装配置1.1 介绍1.2 安装1.3 配置1.3.1 Export Postman1.3.2 Export Yapi1.3.3 Export Markdown1.3.4 Export Api1.3.6 常见问题补充 2. java注释规范2.1 接口注释规范2.2 出入参注释规范 3. 特定化支持3.1 必填校验3.2 忽略导出3.3 返回不一致3.4 设置…...

蓝桥杯练习题总结(三)线性dp题(摆花、数字三角形加强版)

目录 一、摆花 思路一&#xff1a; 确定状态&#xff1a; 初始化&#xff1a; 思路二&#xff1a; 确定状态&#xff1a; 初始化&#xff1a; 循环遍历&#xff1a; 状态转移方程&#xff1a; 二、数字三角形加强版 一、摆花 题目描述 小明的花店新开张&#xff0c;为了吸…...

Elasticsearch(15) multi_match的使用

elasticsearch version&#xff1a; 7.10.1 multi_match是Elasticsearch中的一种查询类型&#xff0c;允许在一个或多个字段上执行全文本搜索&#xff0c;并合并各个字段的结果得分。这种查询有助于实现跨多个字段的统一搜索体验。 语法 {"query": {"multi_m…...

nodejs的线程模型和libuv库的基本使用

文章目录 nodejs中集成addon本地代码的回调问题单线程事件驱动模型libuvlibuv基本框架addon中使用libuv代码nodejs中集成addon本地代码的回调问题 在C++的代码中,回调函数是一个基本的代码调用方式。而在我自己的开发实践中,需要在addon这样一个nodejs的本地化模块中实现一个…...

Uni-app/Vue/Js本地模糊查询,匹配所有字段includes和some方法结合使用e

天梦星服务平台 (tmxkj.top)https://tmxkj.top/#/ 1.第一步 需要一个数组数据 {"week": "全部","hOutName": null,"weekendPrice": null,"channel": "门市价","hOutId": 98,"cTime": "…...

深度学习pytorch——激活函数损失函数(持续更新)

论生物神经元与神经网络中的神经元联系——为什么使用激活函数&#xff1f; 我们将生物体中的神经元与神经网络中的神经元共同分析。从下图可以看出神经网络中的神经元与生物体中的神经元有很多相似之处&#xff0c;由于只有刺激达到一定的程度人体才可以感受到刺激&#xff0c…...

《苹果 iOS 应用开发与分发的关键问题解析》

一、背景 解决同事问的问题&#xff0c;来来回回被问好几次相同的问题&#xff0c;然后确认&#xff0c;我觉得不如写个文档 二、非研发人员安装iOS应用方式 TestFlightIPA 文件 对比 TestFlightIPA 文件安装方式TestFlight 是苹果提供的一个 beta 测试平台&#xff0c;开发者…...

爱上数据结构:顺序表和链表

一、线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条…...

python知识点总结(十)

python知识点总结十 1、装饰器的理解、并实现一个计时器记录执行性能&#xff0c;并且将执行结果写入日志文件中2、队列和栈的区别&#xff0c;并且用python实现3、设计实现遍历目录与子目录4、CPU处理进程最慢的情况通常发生在以下几种情况下&#xff1a;5、CPU处理线程最慢的…...

【Python】探索 Python 编程世界:常量、变量及数据类型解析

欢迎来CILMY23的博客 本篇主题为 探索 Python 编程世界&#xff1a;常量、变量及数据类型解析 个人主页&#xff1a;CILMY23-CSDN博客 Python系列专栏&#xff1a;http://t.csdnimg.cn/HqYo8 上一篇博客&#xff1a; http://t.csdnimg.cn/SEdbp C语言专栏&#xff1a; htt…...

vue页面实现左右div宽度,上下div高度分割线手动拖动高度或者宽度自动变化,两个div宽度或者高度拉伸调节,实现左右可拖动改变宽度的div内容显示区

实现左右或者上下div两部分拖动&#xff0c;宽度或者高度自动变化,实现流畅平滑的变化&#xff0c;还可以是实现拖动到一定宽度就不让拖动了&#xff0c;如果你不需要最小宽度&#xff0c;就直接去掉样式就行 这是页面。分左中右三部分&#xff0c;中间我是用来作为拖动的按钮…...

知攻善防应急靶场-Linux(1)

前言&#xff1a; 堕落了三个月&#xff0c;现在因为被找实习而困扰&#xff0c;着实自己能力不足&#xff0c;从今天开始 每天沉淀一点点 &#xff0c;准备秋招 加油 注意&#xff1a; 本文章参考qax的网络安全应急响应和知攻善防实验室靶场&#xff0c;记录自己的学习过程&am…...

ffmpeg命令行

ffmpeg 如果要在linux gdb 调试&#xff0c;需要在configure 时候不优化 开启调试 ./configure --enable-debug --disable-optimizations make如何开启gdb 调试 gdb ffmpeg_gset args -i test.hevc -c:v copy -c:a copy output_265.mp4rh264 的流生成mp4 文件&#xff0c;不转…...

VMware虚拟机更换引导顺序

前言 我用wmware装了黑群晖测试&#xff0c;将img转成vmdisk的格式之后发现系统引导盘之后1G&#xff0c;有点太小了 我准备把wmware的黑群晖系统迁移到新添加的虚拟磁盘里 1.登录黑群晖的SSH 请先在黑群晖的控制面板中的终端机和SNMP里面启用SSH功能&#xff0c;才能使用ss…...

RAFT:让大型语言模型更擅长特定领域的 RAG 任务

RAFT&#xff08;检索增强的微调&#xff09;代表了一种全新的训练大语言模型&#xff08;LLMs&#xff09;以提升其在检索增强生成&#xff08;RAG&#xff09;任务上表现的方法。“检索增强的微调”技术融合了检索增强生成和微调的优点&#xff0c;目标是更好地适应各个特定领…...

Stable Diffusion 本地训练端口与云端训练端口冲突解决办法

方法之一&#xff0c;修改本地训练所用的端口 1 首先&#xff0c;进入脚本训练器的根目录 例如&#xff1a;C:\MarkDeng\lora-scripts-v1.7.3 找到gui.py 2 修改端口号 因为云端训练器也是占用28000和6006端口 那么本地改成27999和6007也是可以的 保存退出&#xff0c;运行启动…...

C++学习day1

思维导图 定义自己的命名空间&#xff0c;其中有string类型的变量&#xff0c;再定义两个函数&#xff0c;一个函数完成字符串的输入&#xff0c;一个函数完成求字符串长度&#xff0c;再定义一个全局函数完成对该字符串的反转 #include <iostream> using namespace std;…...

openGauss CM

CM 可获得性 本特性自openGauss 3.0.0版本开始引入。 特性简介 CM&#xff08;Cluster Manager&#xff09;是一款数据库管理软件&#xff0c;由cm_server和cm_agent组成。 cm_agent是部署在数据库每个主机上&#xff0c;用来启停和监控各个数据库实例进程的数据库管理组件…...

北斗短报文+4G应急广播系统:实时监控 自动预警 保护校园安全的新力量

安全无小事&#xff0c;生命重如山。学生是祖国的未来&#xff0c;校园安全是全社会安全工作的一个重要的组成部分。它直接关系到青少年学生能否安健康地成长&#xff0c;关系到千千万万个家庭的幸福安宁和社会稳定。 灾害事故和突发事件频频发生&#xff0c;给学生、教职员工…...

2024河北石家庄矿业矿山展览会|河北智慧矿山展会|河北矿博会

2024中国&#xff08;石家庄&#xff09;国际矿业博览会      时间&#xff1a;2024年7月4-6日 地点&#xff1a;石家庄国际会展中心.正定      随着全球经济的持续增长和矿产资源需求的不断攀升&#xff0c;矿业行业正迎来前所未有的发展机遇。作为矿业领域的盛会&…...

ruoyi使用笔记

1.限流处理 RateLimiter PostMapping("/createOrder") ApiOperation("创建充值订单") RateLimiter(key CacheConstants.REPEAT_SUBMIT_KEY,time 10,count 1,limitType LimitType.IP) public R createOrder(RequestBody Form form) {//业务处理return …...

论文《Exploring to Prompt for Vision-Language Models》阅读

论文《Exploring to Prompt for Vision-Language Models》阅读 论文概况论文动机&#xff08;Intro&#xff09;MethodologyPreliminaryCoOp[CLASS]位置Context 是否跨 class 共享表示和训练 ExperimentsOverall ComparisonDomain GeneralizationContext Length (M) 和 backbon…...

科普 | Runes 预挖矿概念

作者&#xff1a;Jacky X/推&#xff1a;zxl2102492 关于 Runes 协议的前世今生&#xff0c;可以点击阅读这篇文章 &#x1f447; 《简述 Runes 协议、发展历程及最新的「公开铭刻」发行机制的拓展讨论》 什么是传统预挖矿概念 这轮比特币生态爆发之前&#xff0c;预挖矿&…...

蓝桥杯真题Day40 倒计时19天 纯练题!

蓝桥杯第十三届省赛真题-统计子矩阵 题目描述 给定一个 N M 的矩阵 A&#xff0c;请你统计有多少个子矩阵 (最小 1 1&#xff0c;最大 N M) 满足子矩阵中所有数的和不超过给定的整数 K? 输入格式 第一行包含三个整数 N, M 和 K. 之后 N 行每行包含 M 个整数&#xf…...

Android 14.0 SystemUI下拉状态栏增加响铃功能

1.概述 在14.0的系统产品rom定制化开发中,在对systemui的状态栏开发中,对SystemUI下拉状态栏的QuickQSPanel区域有快捷功能键开关,对于增加各种响铃快捷也是常用功能, 有需要增加响铃功能开关功能,接下来就来分析SystemUI下拉状态栏QuickQSPanel快捷功能键开关的相关源码…...

docker学习笔记 二-----docker介绍

老套路哈&#xff0c;第一章先科普一下三种常见的云服务类型&#xff0c;第二和第三章节写docker学习笔记。 一 、IAAS、PAAS、SAAS IAAS (Infrastructure as a Service)&#xff1a;基础即服务&#xff0c;供应商仅提供给用户最基础设施的服务资源&#xff0c;也就是服务器资…...