简单来说:就是父亲的东西,都会被儿子继承下来。
注意:构造函数,析构函数,拷贝构造函数,拷贝赋值函数不能被子类继承
老子的特有属性都被儿子继承了下来,所以儿子类中是不是就可以不用再写父类中的属性与方法了。这样就提高了代码的复用性。
同时儿子类中我们还可以添加一些儿子类独有的新特性。这样就提高了代码的拓展性。
所以说继承关系的用意是:代码复用性与高拓展性。就如同这个缤纷复杂的世界,我们可以找到很多的现实的例子。
使用继承关系,需要同学们看待这个世界的事物要上升一个维度,要具有一些抽象的思维。
首先要把这一类的事件进行抽象出来一些共有的属性与特征,把它们定义为父类。然后,再用这个父类,对具有不同特点的子类进行派生。这样就可以派生出各种不同的子类。子类不仅拥有父类的共有的特性,与具备子类独用的特性。这样的代码的复用性与拓展性就会非常灵
- class + 子类 : 继承方式 + 父类
- {
- //单继承的方式
- };
继承方式有三种:
public:公有继承
protected:受保护继承
private:私有继承
当继承方式与类中访问权限的结合时,类内属性到子类之中的访问权限的改变如图所示:

- #include
-
- using namespace std;
-
- class Car{
- private:
- int weight;
- public:
- Car()
- {
- cout<<"Car的构造"<
- }
-
- ~Car()
- {
- cout<<"Car的析构"<
- }
-
- void run()
- {
- cout<<"Car正在行驶过程中"<
- }
-
- 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<<"宝马的构造"<
- }
-
- ~Bwm()
- {
- cout<<"宝马的析构"<
- }
- };
-
- int main()
- {
-
- return 0;
- }
2.4单继承关系的内存布局:

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

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

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

2.7.3证明是包含关系的代码(可以通过父类的直接访问到子类的数值)
- #include
-
- using namespace std;
-
- class A{
- public:
- int one=10;
- };
-
- class B:public A
- {
- public:
- int two=20;
- };
-
- int main()
- {
- A* a=new B;
-
- cout<
one< - cout<<(a+1)->one<
-
- cout<<static_cast(a)->two<
-
- 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
-
- 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
-
- 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)<
- 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
-
- 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)<
- cout<<sizeof (C)<
-
-
相关阅读:
RocketMQ整体架构及NameServer源码分析
用Python订正数据
207.课程表
Java:实现动态数组类算法(附完整源码)
TDengine3.0 基础操作
漏洞复现 - - - WebLogic反序列化远程命令执行漏洞(二)
GDB调试技巧汇总
[Python进阶] 目录相关库:os、pathlib、shutil
.Net Core中使用DiagnosticSource进行日志记录
MySQL进阶实战1,数据类型与三范式
-
原文地址:https://blog.csdn.net/a2998658795/article/details/126020445