c++:继承(超详解)

目录

一:什么是继承

二:继承的格式

继承的总结:

二:子类和父类(基类和派生类)

1.子类和父类的相互赋值:

2.同名的成员变量

3.同名成员函数

三:子类中默认的成员函数

1.构造函数

2.析构函数

3.拷贝构造

4.赋值运算符重载

 四:单继承和多继承

单继承:

 多继承:

菱形继承

解决方法一:

解决方法二:

单继承和多继承的总结:


一:什么是继承

定义:

继承(inheritance)机制是面向对象程序设计中使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生的新类,称派生类(或子类),被继承的类称基类(或父类)。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。之前接触的复用都是函数复用,继承是类设计层次的复用。

好吧,光看也看不出个啥,还是直接上代码吧

代码:

class human {//定义了一个父类,名字叫human
public:string name = "小明";//父类里面定义了一个string类型的和一个int类型int age = 18;
};
class student:public human {//定义了一个以public方式继承父类的子类student
public:int schoolnum = 666;//在父类的name和age的基础上增加了一个schoolnumvoid print(){cout << name << endl << age << endl << schoolnum << endl;//输出}
};
int main()
{student st;st.print();return 0;
}

结果如下:

 好吧是不是还是看不懂,那让我们把这个代码分成两半

第一部分:

class human {
public:string name = "小明";int age = 18;
};

第二部分:

class student:public human {
public:int schoolnum = 666;void print(){cout << name << endl << age << endl << schoolnum << endl;}
};

这样我们发现,其实第一部分的代码就是我们平时使用的class。

对于第二部分的解读我们先举一个例子

如果我们要设计一个学校系统,那么对于学生,老师.....一系列的人,我们都是需要将姓名和年龄等必要信息录入,但是单独针对到某一类人,比如学生,除了必要信息外,还单独有一个学号。比如老师除了必要信息外,还有一个单独的职工号。

 所以为了偷懒提高效率,我们这里就可以把姓名和年龄封装到一个class类里面,也就是我们第一部分的代码,然后再新创一个类,继承原有姓名和年龄类的基础上,再新增学号/职工号,也就是我们第二类。

所以说怎么继承呢?

二:继承的格式

class 新类的名字:继承方式 继承类的名字{};

以我刚才的例子为例

class student:public human{};
//student是新类的名字,public是继承方式,human是要继承的类
//意思就是说我定义了一个名叫 student的类 以public的方式 来继承你human

我们这里对于student和human就有两种叫法。

一种是教科书里面的基类(human)和派生类(student)。

我本人喜欢第二种父类(human)和子类(student)。毕竟感觉就像继承家产一样。

三:继承后的子类成员访问权限

这里我先丢一张图在,这是教科书里面老师铁定要求背诵的

在这里插入图片描述

 我这里分享一个很巧妙的方式

我们假设

public>protectd>private

我们取x和y中,两个较小的。

 最后一个private就都不可。这样我们就很轻松的记忆了下来

继承的总结:

1.基类private成员无论以什么方式继承到派生类中都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2.基类private成员在派生类中不能被访问,如果基类成员不想在派生类外直接被访问,但需要在派生类中访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.基类的私有成员在子类都是不可见;基类的其他成员在子类的访问方式就是访问限定符和继承方式中权限更小的那个(权限排序:public>protected>private)。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。

二:子类和父类(基类和派生类)

1.子类和父类的相互赋值:

代码:

class human {//父类
public:string name = "小明";int age = 18;
};
class student:public human {//子类
public:int schoolnum = 666;
};
int main()
{student st;human hm;hm = st;//将子类赋值给父类st = hm;//将父类赋值给子类return 0;
}

就会出现这样的结果:

 在这里我们引入一个叫做切片原则的东西

 因为父类中没有schoolnum,所以父类接收子类传过来的name和age之后,多余的schoolnum就不管了。但是如果父类传给子类,少传一个,所以会报错。

同时我们给出三种赋值方式

三种方式的赋值:

一:=符号

student st;//子类human hm;//父类hm = st;

 

二:引用

student st;//子类human& hm=st;父类

三:指针

student st;//子类human* hm=&st;//父类

2.同名的成员变量

在有些时候,父类和子类中出现了同一个成员变量,如下name

class human {
public:string name = "小明";
};
class student:public human {
public:string name = "小红";void print(){cout << name << endl;}
};
int main()
{student st;st.print();return 0;
}

这个时候编译器是以子类为优先

结果如下

但是如果我们就是想要访问父类的该成员变量,就需要加上修饰

void print(){cout << human::name << endl;}

 其实也很好理解,默认子类,父类就修饰限定

3.同名成员函数

如下,同样一个函数print在父类和子类中都存在

class human {
public:string name = "小明";void print(){cout << name << endl;}
};
class student :public human {
public:string name = "小红";void print(){cout << name << endl;}
};
int main()
{student st;st.print();return 0;
}

这就构成了隐藏。(函数重载是在同一个作用域,这里父类和子类是两个作用域)

函数的隐藏,编译器会默认调用子类中匹配的函数,如果没有编译器就会报错

上面的结果如下

虽然成员函数的隐藏,只需要函数名相同就构成隐藏,对参数列表没有要求。

 但是我们修改一下子类的函数

void print(int x)//我们对子类的print函数加入一个参数{cout << name << endl;}

 这是因为编译器默认调用子类中print函数,但是子类中唯一的print函数有一个默认的参数,所以编译器无法找到匹配的print函数,所以就会报错。

三:子类中默认的成员函数

1.构造函数

编译器会默认先调用父类的构造函数,再调用子类的构造函数,如下

class human {
public:human(string name = "小明")//先调用:父类默认构造调用一个print打印name:_name(name){cout << name << endl;}
protected:string _name;
};class student :public human {//后调用:子类默认构造调用一个print打印name和age
public:student(string name,int age):_age(age){cout << name << endl<<age<<endl;}
protected:int _age;
};int main()
{student st("小红", 18);return 0;
}

 结果如下

 可以看到,编译器先调用了父类的,打印出了小明,然后再次调用了子类的打印出了小红和age。

所以说请务必保证父类构造有效,假如父类失效

human(string name)//你这里不传值,那么就不能完成初始化,相当于父类失效:_name(name){cout << name << endl;}

那么就必须在子类中给父类构造赋值

student(string name,int age):_age(age), human(name)//新增,子类以自己的name给父类的析构中的name赋值,age和name的顺序随意变动

结果如下

 实在不行就把父类的构造删了,反正编译器也默认会生成的

2.析构函数

析构函数和构造函数相反,编译器默认先调用子类的析构函数,再调用父类的析构函数。

验证如下:

我们在原有的代码上,加入两个析构函数

class human {
public:human(string name = "小明"):_name(name){}~human(){cout << "我是父类" << endl;}
protected:string _name;
};
class student :public human {
public:student(string name,int a = 20):age(a){}~student(){cout <<"我是子类"<< endl;}
protected:int age;
};
int main()
{student st("小明", 18);return 0;
}

结果如下:

 所以说

千万不要在子类中调用父类的析构

千万不要在子类中调用父类的析构

千万不要在子类中调用父类的析构

如果是指针类型,那么同一块区域被析构两次就会造成野指针的问题。

3.拷贝构造

子类中调用父类的拷贝构造时,直接传入子类对象即可,父类的拷贝构造会通过“切片”拿到父类的那一部分。

class human {
public:human(string name="小明"):_name(name){cout << name << endl;}
protected:string _name;
};
class student:public human {
public:student(string name, int age):_age(age){cout << name << endl << age << endl;}student(student& s):human(s)//直接将st传过来通过切片拿到父类中的值,_age(s._age)//拿除了父类之外的值{cout << s._age << endl<<s._name<<endl;}
protected:int _age;
};
int main()
{student st("小红",18);student st2(st);return 0;
}

结果如下:

4.赋值运算符重载

子类的operator=必须要显式调用父类的operator=完成父类的赋值。

因为子类和父类的运算符,编译器默认给与了同一个名字,所以构成了隐藏,所以每次调用=这个赋值运算符都会一直调用子类,会造成循环,所以这里的赋值要直接修饰限定父类

class human {
public:human(string name = "小明"):_name(name){}human& operator=(const human& p){if (this != &p){cout << "调用父类" << endl;_name = p._name;}return *this;}
protected:string _name;
};
class student :public human {
public:student(string name, int age):_age(age){}student(student& s):human(s), _age(s._age){}student& operator=(const student& s){if (this != &s){cout << "调用了子类" << endl;human::operator=(s);//必须调用父类运算符_age = s._age;_name = s._name;}return *this;}
protected:int _age;
};
int main()
{student st("小红", 18);student st2(st);student st3("小刚", 16);st = st3;return 0;
}

 结果如下:

 四:单继承和多继承

单继承:

一个子类只有一个直接父类的继承关系。

 多继承:

一个子类有两个或以上直接父类的继承关系。

 由以上两点,我们就会发现一个很蛋疼厉害的继承,

菱形继承

好,我们先上一段经典菱形继承代码 

这是个代码是有问题的

class A {
public:string name;
};
class B :public A {
public:int age;
};
class C :public A {
public:string sex;
};
class D :public B, public C {
public:int id;
};
int main()
{D student;student.name = "小明";student.age = 18;student.sex = "男";student.id = 666;return 0;
}

啪的一下,很快啊,报错就出来了 

 因为这里的name,同时存在B和C中,所以D不知道继承B的name还是C中的name

这也就是引出了代码冗余和二义性的问题。

所以我们有两种解决方法

解决方法一:

加修饰限定

student.B::name = "小明";

这里我们指定继承B中的name,就不会冲突了

解决方法二:

虚继承:在继承方式前加上virtual。

class B :virtual  public A {
public:int age;
};
class C :virtual public A {
public:string sex;
};

单继承和多继承的总结:

别用菱形继承就完了

多继承是C++复杂的一个体现。有了多继承,就存在菱形继承,为了解决菱形继承,又出现了菱形虚拟继承,其底层实现又很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/231364.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言基础--#if与#endif

目录 一、C语言中的 #if()和 #end if 用法 1. #if 表达式 程序段 #endif 形式 2. #ifdef标示符 标识符 #endif 形式 3. #if 0/ #if 1 #endif 形式 4. \可用于一行的结尾&#xff0c;表示本行与下一行连接起来 二、xTaskCreate函数 三、指针相关…

一个具有电子杂志的模板平台,制作起来事半功倍!

平时大家都是怎么做电子杂志的呢&#xff1f;用什么软件来做呢&#xff1f;现在&#xff0c;越来越多的企业开始将传统的纸质杂志转变为电子杂志。电子杂志不仅可以节省印刷成本&#xff0c;还能为读者提供更加丰富的阅读体验。那么&#xff0c;如何快速制作电子杂志呢&#xf…

【Android Jetpack】Room数据库

文章目录 引入EntitiesPrimary Key主键索引和唯一性对象之间的关系外键获取关联的Entity对象嵌套对象Data Access Objects&#xff08;DAOs&#xff09;使用Query注解的方法简单的查询带参数查询返回列的子集可被观察的查询 数据库迁移用法 引入 原始的SQLite有以下两个缺点: …

【刷题】DFS

DFS 递归&#xff1a; 1.判断是否失败终止 2.判断是否成功终止&#xff0c;如果成功的&#xff0c;记录一个成果 3.遍历各种选择&#xff0c;在这部分可以进行剪枝 4.在每种情况下进行DFS&#xff0c;并进行回退。 199. 二叉树的右视图 给定一个二叉树的 根节点 root&#x…

Cytoscape软件下载、安装、插件学习[基础教程]

写在前面 今天分享的内容是自己遇到问题后&#xff0c;咨询社群里面的同学&#xff0c;帮忙解决的总结。 关于Cytoscape&#xff0c;对于做组学或生物信息学的同学基本是陌生的&#xff0c;可能有的同学用这个软件作图是非常溜的&#xff0c;做出来的网络图也是十分的好看&am…

(亲测有效)解决windows11无法使用1500000波特率的问题

大家好&#xff01;我是编码小哥&#xff0c;欢迎关注&#xff0c;持续分享更多实用的编程经验和开发技巧&#xff0c;共同进步。 1、问题描述 从图1可以看出串口是正常的&#xff0c;安装的驱动是CP210xVCPInstaller_x64.exe&#xff0c;但是从图2可以看出&#xff0c;串口拒…

Java数据结构之《顺序查找》问题

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等偏下的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我…

解决:ValueError: must have exactly one of create/read/write/append mode

解决&#xff1a;ValueError: must have exactly one of create/read/write/append mode 文章目录 解决&#xff1a;ValueError: must have exactly one of create/read/write/append mode背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用…

【存储】blotdb的原理及实现(2)

【存储】etcd的存储是如何实现的(3)-blotdb 在etcd系列中&#xff0c;我们对作为etcd底层kv存储的boltdb进行了比较全面的介绍。但是还有两个点没有涉及。 第一点是boltdb如何和磁盘文件交互。 持久化存储和我们一般业务应用程序的最大区别就是其强依赖磁盘文件。一方面文件数…

基于springboot+vue的在线考试系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

顺丰JAVA开发一面—面试实战经验分析【已通过】

文章目录 面试总结面试开始项目相关基础知识反问环节 顺丰JAVA开发一面面试过程中的问题确实涵盖了很多方面&#xff0c;从项目架构到基础知识再到具体技术细节都有所涉及。 面试官的提问风格也是比较开放的&#xff0c;注重考察面试者的深度理解和解决问题的能力。以下是对每个…

AI模型训练——入门篇(一)

前言 一文了解NLP&#xff0c;并搭建一个简单的Transformers模型&#xff08;含环境配置&#xff09; 一、HuggingFace 与NLP 自从ChatGPT3 问世以来的普及性使用&#xff0c;大家或许才真正觉察AI离我们已经越来越近了&#xff0c;自那之后大家也渐渐的开始接触stable diff…