1. 钻石继承与虚继承
2. 什么是钻石继承?
ANSWER:假设我们已经有了两个类Father1和Father2,他们都是类GrandFather的子类。现在又有一个新类Son,这个新类通过多继承机制对类Father1和Father2都进行了继承,此时类GrandFather、Father1、Father2和Son的继承关系是一个菱形,仿佛一个钻石,因此这种继承关系在C++中通常被称为钻石继承(或菱形继承)。
示例:
#include
using namespace std;
class GrandFather{
//第一层基类GrandFather
public:
GrandFather()=default;
GrandFather(int v):value(v){}
int value;
};
class Father1:public GrandFather{
//第二层基类Father1
public:
Father1()=default;
Father1(int v):GrandFather(v){}
void set_value(int value){ //设置value的值
this->value=value;
}
};
class Father2:public GrandFather{ //第二层基类Father2
public:
Father2()=default;
Father2(int v):GrandFather(v){}
int get_value(){ //获取value的值
return this->value;
}
};
class Son:public Father1,public Father2{ //第三次层类Son
public:
Son()=default;
Son(int v):Father1(v),Father2(v){}
};
int main(){
Son s(10);
s.set_value(20);
cout
QUESTION:上例中明明将对象s的value值设置成了20,为什么最终value的输出却还是初始化值10?
**ANSWER:**解决这个问题我们首先需要知道对象s是如何构造的,还是上面的示例:
#include
using namespace std;
class GrandFather{ //第服务器托管网一层基类GrandFather
public:
GrandFather()=default;
GrandFather(int v):value(v){
coutvalue=v;
}
};
class Father2:public GrandFather{ //第二层基类Father2
public:
Father2()=default;
Father2(int v):GrandFather(v){
coutvalue;
}
};
class Son:public Father1,public Father2{ //第三次子类Son
public:
Son()=default;
Son(int v):Father1(v),Father2(v){
cout
我们发现在创建类Son的对象s时,第一层基类GrandFather的构造函数被调用了两次,这说明系统在创建对象s前会先创建两个独立的基类子对象(分别是Father1的对象和Father2的对象),然后再创建包含这两个子对象的对象s,如图:
由此可见,对象s中包含两个分属于不同子对象的成员变量value。而方法set_value()
和方法get_value()
虽然都是对象s的成员函数,但由于其也分属于对象s中的不同子对象,故其操作所针对的成员变量value不是同一个value,而是方法所在的子对象所包含的value,即上例中方法set_value()
的功能是重新设置Father1类所创建的子对象的value值,而方法get_value()
是返回Father2类所创建的子对象的value值。
int main(){
Son s(10);
s.set_value(20);
cout
3. 如何解决钻石继承中存在的“数据不一致”问题?
ANSWER:在C++中通常利用虚基类和虚继承来解决钻石继承中的“数据不一致”问题
特别注意:
1.什么是虚继承和虚基类
• 虚继承:在继承定义中包含了virtual关键字的继承关系
• 虚基类:在虚继承体系中通过关键字virtual继承而来的基类
2.为什么使用虚基类和虚继承
• 使用虚基类和虚继承可以让一个指定的基类在继承体系中将其成员数据实例共享给从该基类直接或间接派生出的其它类,即使从不同路径继承来的同名数据成员在内存中只有一个拷贝,同一个函数名也只有一个映射。
#include
using namespace std;
class GrandFather{ //第一层基类GrandFather
public:
GrandFather()=default;
GrandFather(int v):value(v){
coutvalue=value;
}
};
class Father2:virtual public GrandFather{ //第二层基类Father2,虚继承基类GrandFather
public:
Father2()=default;
Father2(int v):GrandFather(v){
coutvalue;
}
};
class Son:public Father1,public Father2{ //第三次子类Son
public:
Son()=default;
Son(int v):Father1(v),Father2(v),GrandFather(v) {
cout
上例中的钻石继承中,由于基类Father1和基类Father2采用虚继承的方式来继承类GrandFather,此时对象s中类Father1和类Father2创建的子对象共享GrandFather类创建的子对象,如图:
此时对象s中成员变量value只有一个,且被Father1类创建的子对象和Father2类创建的子对象所共享,即方法set_value()和方法get_value()操作的value是同一个成员变量。
3.构造函数的调用顺序
• 首先按照虚基类的声明顺序调用虚基类的构造函数
• 然后按照非虚基类的声明顺序调用非虚基类的构造函数
• 之后调用派生类中成员对象的构造函数
• 最后调用派生类自己的构造函数
示例:
#include
using namespace std;
class One{
public:
int one;
One(int o):one(o){
cout
4.使用虚基类和虚继承时的一些注意事项:
-
在派生类对象中,同名的虚基类只产生一个虚基类子对象,而同名的非虚基类则各产生一个非虚基类子对象
-
虚基类的子对象是由最后派生出来的类的构造函数通过调用虚基类的构造函数来初始化的。因此在派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的默认构造函数。
Six six(10);
return 0;
服务器托管网}
[外链图片转存中...(img-ICHYtE7h-1710397019446)]
**4.使用虚基类和虚继承时的一些注意事项:**
- **在派生类对象中,同名的虚基类只产生一个虚基类子对象,而同名的非虚基类则各产生一个非虚基类子对象**
- **虚基类的子对象是由最后派生出来的类的构造函数通过调用虚基类的构造函数来初始化的。因此在派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的默认构造函数。**
- **虚基类并不是在声明基类时声明的,而是在声明派生类时通过指定其继承该基类的方式来声明的。**
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
相关推荐: 实战案例:将已有的 MySQL8.0 单机架构变成主从复制架构
操作步骤 修改 master 主节点 的配置( server-id log-bin ) master 主节点 完全备份( mysqldump ) master 主节点 创建复制用户并授权 master 主节点 将完全备份文件拷贝至从节点 修改 slave 从节…