栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

c++笔记 继承

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

c++笔记 继承

一、父类和子类的同名成员函数处理

1子类继承父类所有成员函数和成员变量

class base
 {
 public:
 void func(void)
 {
 cout<<"父类中的void func"< 

多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么 通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类 1 继承的版 本还是从基类 2 继承的版本? 解决方法就是显示指定调用那个基类的版本。

菱形继承

两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为 菱形继承,或者钻石型继承。

 这种继承所带来的问题:

1. 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用 函数或者数据时,就会产生二义性。

2. 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我 们只需要一份就可以。

class Bigbase{
public:
Bigbase(){ mParam = 0; }
void func(){ cout << "Bigbase::func" << endl; }
public:
int mParam;
};
class base1 : public Bigbase{};
class base2 : public Bigbase{};
class Derived : public base1, public base2{};
int main(){
Derived derived;
//1. 对“func”的访问不明确
//derived.func();
//cout << derived.mParam << endl;
cout << "derived.base1::mParam:" << derived.base1::mParam << endl;
cout << "derived.base2::mParam:" << derived.base2::mParam << endl;
//2. 重复继承
cout << "Derived size:" << sizeof(Derived) << endl; //8
return EXIT_SUCCESS;
}

上述问题如何解决?对于调用二义性,那么可通过指定调用那个基类的方式来解 决,那么重复继承怎么解决? 对于这种菱形继承所带来的两个问题,c++为我们提 供了一种方式,采用虚基类。那么我们采用虚基类方式将代码修改如下:

class Bigbase{
public:
Bigbase(){ mParam = 0; }
void func(){ cout << "Bigbase::func" << endl; }
public:
int mParam;
};
class base1 : virtual public Bigbase{};
class base2 : virtual public Bigbase{};
class Derived : public base1, public base2{};
int main(){
Derived derived;
//二义性问题解决
derived.func();
cout << derived.mParam << endl;
//输出结果:12
cout << "Derived size:" << sizeof(Derived) << endl;
return EXIT_SUCCESS;
}

以上程序 base1 ,base2 采用虚继承方式继承 Bigbase,那么 Bigbase 被称为虚基 类。 通过虚继承解决了菱形继承所带来的二义性问题。 但是虚基类是如何解决二 义性的呢?并且 derived 大小为 12 字节,这是怎么回事?

虚继承实现原理
class Bigbase{
public:
Bigbase(){ mParam = 0; }
void func(){ cout << "Bigbase::func" << endl; }
public: int mParam;
};
#if 0 //虚继承
class base1 : virtual public Bigbase{};
class base2 : virtual public Bigbase{};
#else //普通继承
class base1 : public Bigbase{};
class base2 : public Bigbase{};
#endif
class Derived : public base1, public base2{};

 

 

 

 

 通过内存图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜 测到编译器肯定对我们编写的程序做了一些手脚。

Bigbase 菱形最顶层的类,内存布局图没有发生改变。 base1 和 base2 通过虚继 承的方式派生自 Bigbase,这两个对象的布局图中可以看出编译器为我们的对象中增 加了一个 vbptr (virtual base pointer),vbptr 指向了一张表,这张表保存了当前的虚 指针相对于虚基类的首地址的偏移量。

Derived 派生于 base1 和 base2,继承了两 个基类的 vbptr 指针,并调整了 vbptr 与虚基类的首地址的偏移量。 由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承 一份数据,并且也解决了二义性的问题。现在模型就变成了 base1 和 base2 Derived 三个类对象共享了一份 Bigbase 数据。

当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对 象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使 共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被 初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成 初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一 次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成, 其他的初始化语句都不会调用

 

class Bigbase{
public:
Bigbase(int x){mParam = x;}
void func(){cout << "Bigbase::func" << endl;}
public:
int mParam;
};
class base1 : virtual public Bigbase{
public:
base1() :Bigbase(10){} //不调用 Bigbase 构造
};
class base2 : virtual public Bigbase{
public:
base2() :Bigbase(10){} //不调用 Bigbase 构造
};
class Derived : public base1, public base2{
public:
Derived() :Bigbase(10){} //调用 Bigbase 构造
};
//每一次继承子类中都必须书写初始化语句
int main(){
Derived derived;
return EXIT_SUCCESS;
}

 注意: 虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没 有公共祖先的多继承的. 工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性 远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法 上,任何多继承都可以用单继承代替。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/780041.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号