1.继承关系(is a关系)
1.1概述
简单来说:就是父亲的东西,都会被儿子继承下来。
注意:构造函数,析构函数,拷贝构造函数,拷贝赋值函数不能被子类继承
1.2.继承关系的用意和目的是什么?
老子的特有属性都被儿子继承了下来,所以儿子类中是不是就可以不用再写父类中的属性与方法了。这样就提高了代码的复用性。
同时儿子类中我们还可以添加一些儿子类独有的新特性。这样就提高了代码的拓展性。
所以说继承关系的用意是:代码复用性与高拓展性。就如同这个缤纷复杂的世界,我们可以找到很多的现实的例子。
使用继承关系,需要同学们看待这个世界的事物要上升一个维度,要具有一些抽象的思维。
首先要把这一类的事件进行抽象出来一些共有的属性与特征,把它们定义为父类。然后,再用这个父类,对具有不同特点的子类进行派生。这样就可以派生出各种不同的子类。子类不仅拥有父类的共有的特性,与具备子类独用的特性。这样的代码的复用性与拓展性就会非常灵
2继承关系的分类
2.1单继承的方式:
class + 子类 : 继承方式 + 父类 { //单继承的方式 };
讯享网
2.2继承方式:
继承方式有三种:
public:公有继承
protected:受保护继承
private:私有继承
当继承方式与类中访问权限的结合时,类内属性到子类之中的访问权限的改变如图所示:
2.3简单的例子
讯享网#include <iostream> using namespace std; class Car{ private: int weight; public: Car() { cout<<"Car的构造"<<endl; } ~Car() { cout<<"Car的析构"<<endl; } void run() { cout<<"Car正在行驶过程中"<<endl; } int setweight(int weight) { this->weight=weight; return this->weight; } }; class Bwm:public Car { private: string logo; public: Bwm(string logo,int weigth) { this->logo=logo; this->setweight(weigth); cout<<"宝马的构造"<<endl; } ~Bwm() { cout<<"宝马的析构"<<endl; } }; int main() { return 0; }
2.4单继承关系的内存布局:

子类在定义对象时,先创建子类的空间,然后构造顺序是:先调用父类的构造对父类中的属性完成初始化,然后再调用子类的构造完成对子类属性的初始化。当子类对象被销毁时,析构的顺序是:首先调用子类的析构,然后再调用父类的析构,最后资源就被回收
2.5当使用继承时,如果父类中没有默认构造,需要在子类的初始化列表指定编译器所应该调用父类构造。
#include <iostream> using namespace std; class Car{ private: int weight; public: Car(int weigth) { this->weight=weight; cout<<"Car的构造"<<endl; } ~Car() { cout<<"Car的析构"<<endl; } void run() { cout<<"Car正在行驶过程中"<<endl; } int setweight(int weight) { this->weight=weight; return weight; } }; class Bwm:public Car { private: string logo; public: Bwm(string logo,int weigth):Car(1) { this->logo=logo; this->setweight(weigth); cout<<"宝马的构造"<<endl; } ~Bwm() { cout<<"宝马的析构"<<endl; } }; int main() { return 0; }
2.6当父类中有与子类中的属性或方法同名时,父类中的同名属性或方法,将被自动隐藏在父类的类域之中。
讯享网#include <iostream> using namespace std; class A{ public: int a=10; }; class B:public A { public: int a=20; }; int main() { B b; cout<<b.a<<endl; cout<<b.A::a<<endl; return 0; }
结果图:

2.7C++中继承关系下的内存布局与类型兼容规则:
is a关系是一种特殊的has a关系:也和包含关系一样,起始地址是一样的,可以通过父类访问到子类。

2.7.1证明起始地址是一样的
#include <iostream> using namespace std; class A{ public: int one=10; A() { cout<<"父类的起始地址"<<this<<endl; } }; class B:public A { public: int two=20; B() { cout<<"子类的起始地址"<<this<<endl; } }; int main() { B b; return 0; }
结果图:

2.7.3证明是包含关系的代码(可以通过父类的直接访问到子类的数值)
讯享网#include <iostream> using namespace std; class A{ public: int one=10; }; class B:public A { public: int two=20; }; int main() { A* a=new B; cout<<a->one<<endl; cout<<(a+1)->one<<endl; cout<<static_cast<B*>(a)->two<<endl; return 0; }
结果图:


所以在单继承情况下,父类指针与子类指针保持一致,父类指针可以天然且安全指向父类对象。
这就是单继承情况下的类型兼容规则,反之则不可以。
3多继承及棱形继承的相关问题及解决方案
3.1多继承的语法:
class + 子类 : 继承方式 + 父类1,继承方式 + 父类2,继承方式 + 父类3,... { //多继承的方式 };
3.2多继承的实例
3.2.1当我们这样写的时候多继承就会出现二义性,如图:

3.2.2而且如果像这样我们只想要power和e的时候我们会继承很多我们不需要的东西,造成代码膨胀的问题
讯享网using namespace std; class Phone{ public: int power; int a; int b; }; class Competer { public: int c; int d; int f; }; class Notebook:public Phone,public Competer { Notebook(int power) { this->power=power; } }; int main() { return 0; }
3.3解决方案:
一般在使用多继承时,使用多继承多个抽象类,而且实体体。这样就可以避免以上的问题。如果,一定要继承多个实体时,在访问属性或方法时,一定要加上具体的父类的类域,这样也可以避免同名属性或方的二义性的问题,如下。
#include <iostream> using namespace std; class Phone{ public: int power; }; class Competer { public: int power; }; class Notebook:public Phone,public Competer { Notebook(int power) { this->Competer::power=power; } }; int main() { return 0; }
4棱形继承及相关问题及解决方案:
4.1棱形继承图:

4.2菱形的缺点
4.2.1如下代码所示,我们用代码来说明问题,总结在结果图处。
讯享网#include <iostream> using namespace std; class A{ public: int a; }; class B:public A { public: }; class C:public A { public: }; class D:public B,public C { public: }; int main() { D d; cout<<sizeof (d)<<endl; return 0; }
结果图:

1.我们在代码中只定义了一个代码为int a的内存,内存大小为4,而到了最远的D时,内存大小为了8,这就导致不管父类有多少内存,最远的那个类型接到的内存大小永远是父类的两倍,导致了最远处的类被多次构造。
这两个和多次继承一样。
2.同名属性与方法的二义性的问题。
3.代码膨胀的问题。
4.3解决方法
4.3.1首先我们说一下内部机制
当使用virtual修饰继承权后,继承类中,编译器就会默默安插了一根虚指针,这个虚指针。这两个直接继承类中各有一根虚基表指针,指向一张共有的虚基表。这张虚基表中存在偏移量,通过偏移量就可以找到共有的那个属性。也就是说B 与 C 是共享了一分虚基类。所以A只需要构造一分,B与C就可以虚基表中的偏移找到A中的属性。
4.3.2代码说明
当我们没有用virtual修饰的时候,内存大小在4.2.1中的代码中有说过,也就是最后D的内存大小为8,当我们加上virtual的时候我们再来看看,代码如下
#include <iostream> using namespace std; class A{ public: int a; }; class B:virtual public A { public: }; class C:virtual public A { public: }; class D:public B,public C { public: }; int main() { cout<<sizeof (B)<<endl; cout<<sizeof (C)<<endl; cout<<sizeof (D)<<endl; return 0; }
结果图:

由结果图来说,我们可以看到B和C的内存大小为16,他们当中各有一个虚指针,大小为8,还有一个共同使用的int a,内存大小为4,因为涉及结构体补齐的问题,所以大小为16,所以D就是继承了两个虚指针,以及B和C共同使用的int a 所以结构体大小为24。
注意:结构体补齐的问题。
4.3.3总结:
在实际工作中,如果使用继承与使用包含关系都可以解决,首选包含关系。
如果单继承与多继承都可以解决,首选单继承。
如果不可避免要要使用多继承,则要多继承多个接口类。
如果不可避免会发发生棱形继承,则要使用虚继承

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/44944.html