C++继承(图文非常详细)

继承的概念

1.什么是继承

1.简单定义

我们来看一下下面这串代码注意其中的两个类father 和 son

using namespace std;
#include<iostream>
class father
{
public:void definity(){cout << "father" << endl;}
protected:int tall = 180;int age = 20;
};
class son : public father
{
public:void definityson(){cout << "son" << endl;cout << tall << endl;}
protected:int tall1 = 200;int age1 = 25;
};
int main()
{son SN;SN.definity();SN.definityson();}

 我们可以发现son类定义的SN可以调用father的函数definity()

 

如上图所示,这便是继承的格式。

 class 类名: 继承方式  继承的类名

 通过这样继承的方式我们可以想想一下,就是将father里面的内容全部让son可见,如果你觉得不好理解,你可以认为就是father里面的内容全部加进son里面,所以son就可以访问father里面的内容。

 

 注释的内容就是son从father那里继承过来的内容,只是没有显示出来,但同样可以使用

 2.使用场景

当我需要定义两个类时,但恰好这两个类的内容有大部分是相同的。

class teacher
{
public:void teach(){cout << "I respond to teach" << endl;}void approach(){cout << " I can admitted" << endl;}
protected:int tall;int age;int rank;
};
class student
{
public:void study(){cout << "I respond to teach" << endl;}void approach(){cout << " I can admitted" << endl;}
protected:int tall;int age;int rank;
};

我们看以上代码有两个类,但这两个类里面的内容大部分是相同的,如果我们还要定义其它的类,也和这两个类大部分的内容是相同的,那么一个一个定义是不是有点太麻烦了。

那么这时候我们只需将相同的部分全部放入一个类A里面,其它的类只要定义自己特殊的那部分,然后再继承类A,那么就完成了我们需要的定义

代码如下

class person
{
public:void approach(){cout << " I can admitted" << endl;}
protected:int tall;int age;int rank;
};
class teacher:public person
{
public:void teach(){cout << "I respond to teach" << endl;cout << rank << endl;}void prove(){rank = 20;}
};
class student:public person
{
public:void study(){cout << "I respond to teach" << endl;cout << rank << endl;}void error(){rank = 10;}
};
int main()
{student xiaoming;teacher laoxu;laoxu.prove();xiaoming.error();laoxu.teach();
}

 3.继承规则

我们来看这一段,当我们用public的方式继承person的时候,变量DNA是不能访问的,但rank可以访问,因为DNA再person的private范围里

 2.特殊的继承方式

1.类模板继承

template<class T>
class Stack : public vector<T>
{
public:void PUSH(const T& x){vector<T>::push_back(x);}void pop(){vector<T>::pop_back();}T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}
};
int main()
{Stack<int> s1;s1.PUSH(1);s1.PUSH(2);s1.PUSH(3);s1.PUSH(4);s1.PUSH(5);s1.PUSH(6);s1.pop();cout << s1.top() << endl;cout << s1.empty() << endl;
}

上述代码中Stack直接继承了库函数中的vector,并且提供了模板参数,这样就大大方便了我们去模拟实现Stack 

2.基类和派生类之间的转换 

public继承的派生类对象可以赋值给基类的指针,形象地说就是,派生类将基类多出来的那部分切割掉,然后再赋值给基类的指针和引用

 

 如下图代码

class person
{
public:void study(){cout << " I can studying " << endl;}void teach(){cout << "I can teaching" << endl;}string name;int age;int sex;
};
class student:public person
{
public:int NO;
};
int main()
{student s1;s1.age = 10;person* p2;p2 = &s1;cout << p2->age << endl;
}

可以通过基类的指针访问派生类的成员 

基类不能赋值给派生类 

 3.隐藏规则

当派生类中有和基类一样的函数,那么派生类便会隐藏基类的函数

class person
{
public:void study(){cout << " I can studying " << endl;}void teach(){cout << "I can teaching" << endl;}
};
class student:public person
{
public:void study(){cout << " He can not to studying" << endl;}
};
int main()
{student s1;s1.study();
}

我们可以看到,当我们调用study函数时,并没有调用基类的study函数,而是调用了派生类的study函数,因为派生类中有和基类同名的函数,所以派生类便隐藏了基类的函数 

当我们需要调用基类的函数时我们需要特殊声明比如 

 

声明格式 基类名称 :函数 

函数隐藏只要是同名便会隐藏,所以不存在所谓重载 

 

并不建议将派生类函数和基类函数用作同一名字 

3.四种派生类的默认成员函数

1.默认构造函数

当我们调用派生类的默认构造函数时,同时也会调用基类的默认构造函数

class person
{
public:person(const char* ch = "xiaoming"){name = ch;cout << "until to person" << endl;}protected:const char* name;
};
class student:public person
{
public:student(){cout << "until to student" << endl;tall = 100;age = 10;}protected:int tall;int age;
};
int main()
{student s1;
}

我们看如上代码 和 结果

当我实例化派生类对象时,同时也会调用基类的默认构造函数,那是因为要用基类的默认构造函数去初始化基类对象。

 2.析构函数

class person
{
public:person(const char* ch = "xiaoming"){name = ch;cout << "until to person" << endl;}~person(){cout << " until to ~person" << endl;}
protected:const char* name;
};
class student:public person
{
public:student(){cout << "until to student" << endl;tall = 100;age = 10;}~student(){cout << "until to ~student" << endl;}
protected:int tall;int age;
};
int main()
{student s1;
}

如上图所示,当我调用派生类对象的析构函数时,我们会先调用派生类对象的析构函数,再调用基类的析构函数,这和默认构造函数的顺序有所不同,我们先初始化基类再初始化派生类,先析构派生类再析构基类 

 3.拷贝构造函数

class person
{
public:person(const char* ch = "xiaoming"){name = ch;cout << "until to person" << endl;}person(const person& ps){name = ps.name;}~person(){cout << " until to ~person" << endl;}
protected:const char* name;
};
class student:public person
{
public:student(){cout << "until to student" << endl;tall = 100;age = 10;}student(const student& st){tall = st.tall;age = st.age;}~student(){cout << "until to ~student" << endl;}void inital(){tall = 180;age = 20;name = "xiaozhang";}
protected:int tall;int age;
};
int main()
{student s1;s1.inital();student s2(s1);
}

如上述代码,我们将s1的数据更改,再用s1的数据去拷贝s2,那么s1和s2的结果会是怎么样呢 

 我们发现s1和s2位于基类中的成员变量不同,这是因为当调用拷贝构造时,并没有调用到基类的拷贝构造,所以导致基类成员的数据没有拷贝进去

我们需要在初始化列表里调用基类的拷贝构造函数

 

在派生类的拷贝构造 初始化列表中调用基类的拷贝构造

 4.重载赋值运算符

class person
{
public:person(const char* ch = "xiaoming"){name = ch;cout << "until to person" << endl;}person(const person& ps){cout << "COPY until to person" << endl;name = ps.name;}person& operator=(const person& st1){cout << "= person until to =" << endl;if (this != &st1){name = st1.name;}return *this;}~person(){cout << " until to ~person" << endl;}
protected:const char* name;
};
class student:public person
{
public:student(){cout << "until to student" << endl;tall = 100;age = 10;}student(const student& st):person(st){cout << "COPY until to student" << endl;tall = st.tall;age = st.age;}~student(){cout << "until to ~student" << endl;}student& operator=(const student& st1){cout << "= person until to =" << endl;if (this!=&st1){person::operator=(st1);tall = st1.tall;age = st1.age;}return *this;}void inital(){tall = 180;age = 20;name = "xiaozhang";}
protected:int tall;int age;
};
int main()
{student s1;s1.inital();student s2;s2 = s1;
}

通过实现这最后一个函数我们可以看看成员函数的调用规则 

 

首先构造两个派生类,分别调用两次基类构造和派生类构造

然后赋值再调用两次赋值函数

析构先析构派生类再析构基类 

4.不能被继承的类

第一种

第一种在基类定义时 名称后面加final 

当我们定义基类时,在后面加final ,那么派生类在继承时就失败了 

第二种 

第二种,将默认构造函数放在private范围内 

 

当我们将基类的默认构造函数放在private里时,派生类在构造对象时会调用基类的默认构造函数,但是因为基类的默认构造函数是存在于private内的,所以会调用失败 

5.继承与友元 

友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员 

using namespace std;
#include<iostream>
class person
{friend void print();
public:person(){cout << "I am a person" << endl;}
private:int gender=0;int tall=0;
};
class student :public person
{
public:student(){cout << "I am a student" << endl;}
private:int grade=0;
};
void print()
{person p1;p1.gender = 10;cout << p1.gender << endl;//print()是基类的友元函数,所以可以访问基类构成的对象private成员student s1;s1.grade = 10;cout << s1.grade << endl;//虽然student继承了person,但友元关系不能继承,所以print()不能访问派生类的private成员
}
int main()
{print();
}

 6.继承与静态成员

基类定义了一个static成员,则整个继承过程中,只会产生一个static,不管继承多少次,都只会有这一个static成员。

using namespace std;
#include<iostream>
class person
{
public:static int st;
private:int gender=0;int tall=0;
};
int person::st = 50;
//类中的静态变量要在类外初始化
class student :public person
{
public:
private:int grade=0;
};
int main()
{student s1;person p1;cout << p1.st << endl;//输出50cout << s1.st << endl;//输出50//不管继承多少次它们的st都是同一个st,因为st存储在静态区中
}

 7.继承模型

1.单继承

 上述类似单继承模型一个基类只被一个派生类继承一次,并且每个派生类都只被一个派生类继承一次

2.多继承 

以上就是多继承模型指的是一个基类或一个派生类被两个或两个以上的派生类所继承 

3.菱形继承 

以上就是菱形继承的模型,指的就是一个派生类对另一个派生类或者基类继承了两次或两次以上 

但是这样的话就会有双份数据,所以会使对象在调用的时候不明确调用那个 

using namespace std;
#include<iostream>
class person
{
public:int gender=0;int tall=0;
};
class student :public person
{
public:
private:int grade=0;
};
class classmate :public person
{
public:
private:int next;
};
class myself : public classmate, public student
{
public:
private:int myself;
};
int main()
{myself MY;cout << MY.gender << endl;//调用gender失败,编译器不知道该调用哪个gendercout << MY.classmate::gender << endl;//调用成功我们需要指出该调用的类范围
}

C++为了解决这样的问题,发明了虚继承 

4.虚继承 

 

当有一个基类被继承两次时我们需要在第一次被继承时加上virtual进行虚继承 

比如 

通过增加virtual来对person进行虚继承,那么这样myself继承时就只会继承一份person 

但是并不建议设计菱形模型,因为这样会复杂非常多 

 5.多继承指针偏移问题

using namespace std;
#include<iostream>
class student
{
public:
private:int grade=0;
};
class classmate
{
public:
private:int next=0;
};
class myself : public classmate, public student
{
public:
private:int myself;
};
int main()
{myself MY;classmate* p1 = &MY;myself* p2 = &MY;student* p3 = &MY;cout <<" classmate : " << p1 << endl;cout <<" myself    : " << p2 << endl;cout <<" student   : " << p3 << endl;}

我们来看上述代码 

myself先是继承了classmate,然后再继承了student那么myself的顺序空间如下 

 

classmate在前student在后 

当我们使用myself类型指针和classmate类型指针和student类型指针指向这个myself对象时指针位置如图所示 

所以classmate和myself在同一位置而student在它们后面。所以代码运行结果如下 

 

 8.继承和组合

1.组合的概念

继承我们已经讲过了,那我们来讲讲组合的方式

 

这便是组合的方式,我们直接在一个类里面构建另外一个类的对象,我们可以使用这个对象的函数。  

2.组合和继承的优缺点 

组合将被组合的内部结构封闭了起来,使我们自己构建的类不能访问它的private和protected,安全性比较高,但继承不一样,继承可以访问,所以安全性比较低,我们能使用组合时尽量使用组合。

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

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

相关文章

解决PyQt5多线程报错:QThread: Destroyed while thread is still running

在使用PyQt5进行多线程开发时&#xff0c;许多初学者会遇到一个常见的问题&#xff1a;程序在运行时没有任何明显的错误信息&#xff0c;但却在终端中报出QThread: Destroyed while thread is still running的警告。而在PyCharm等IDE中&#xff0c;只会看到Process finished wi…

Python中的数据类(dataclass):简化类的定义与数据管理的全面指南

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! Python 3.7引入的数据类(dataclass)极大地简化了类的定义,尤其在处理数据管理任务时。数据类自动生成__init__、__repr__等方法,帮助开…

详细介绍MySQL、Mongo、Redis等数据库的索引

MySQL 索引 概念与类型 MySQL 索引是一种数据结构&#xff0c;用于快速查找数据库中的数据。它就像一本书的目录&#xff0c;通过索引可以快速定位到需要的数据行&#xff0c;而不必全表扫描。主要的索引类型包括 B - Tree 索引&#xff08;默认索引类型&#xff09;、哈希索引…

torch.full函数介绍

torch.full 是 PyTorch 中用于创建一个具有指定形状、填充值和数据类型的张量的函数。它非常适用于需要初始化特定数值的张量的情况,比如将所有元素填充为一个常量值。 函数定义 torch.full(size, fill_value, *, dtype=None, layout=torch.strided, device=None, requires_…

Unity自动打包——Shell交互

Unity 无论是测试还是上线都需要打包&#xff0c;而每次打包我们还要打各种平台&#xff08;安卓、Ios、WebGL、Windows…&#xff09;,有可能不同的打包时机还要有不同的配置项&#xff0c;这种工作枯燥、繁琐且易错&#xff0c;为了解决这一困扰就想到了能不能做一个工具来专…

NLP自然语言处理:深入探索Self-Attention——自注意力机制详解

NLP自然语言处理&#xff1a;深入探索Self-Attention——自注意力机制详解 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;自注意力机制&#xff08;Self-Attention&#xff09;已经成为一种革命性的技术&#xff0c;特别是在Transformer模型及其各种变种中得到了…

Tomcat中如何指定JDK版本

在Tomcat中指定JDK版本可以通过设置环境变量或修改启动脚本来实现。以下是两种常见的方法&#xff1a; 方法一&#xff1a;通过环境变量 设置JAVA_HOME环境变量&#xff1a; 打开“控制面板” -> “系统和安全” -> “系统” -> “高级系统设置”。点击“环境变量”按…

uniapp在js方法中,获取当前用户的uid(uni-id-user)表中的用户id

// 1.判断当前用的权限 let uid uniCloud.getCurrentUserInfo().uid //获取当前用户的uid // 用户uid等于发布者id或者用户权限等于admin或者用户角色等于webmaster if (uid this.item.user_id[0]._id || this.uniIDHasRole…

【机器学习】均方误差根(RMSE:Root Mean Squared Error)

均方误差根&#xff08;Root Mean Squared Error&#xff0c;RMSE&#xff09;是机器学习和统计学中常用的误差度量指标&#xff0c;用于评估预测值与真实值之间的差异。它通常用于回归模型的评价&#xff0c;以衡量模型的预测精度。 RMSE的定义与公式 给定预测值 和实际值 …

Pandas | 数据分析时将特定列转换为数字类型 float64 或 int64的方法

类型转换 传统方法astype使用value_counts统计通过apply替换并使用astype转换 pd.to_numericx对连续变量进行转化⭐参数&#xff1a;返回值&#xff1a;示例代码&#xff1a; isnull不会检查空字符串 数据准备 有一组数据信息如下&#xff0c;其中主要将TotalCharges、MonthlyC…

web信息收集

区别 CTF中&#xff0c;收集服务器信息、敏感信息、敏感文件 &#xff08;实战中&#xff0c;收集更多的信息来找到渗透的突破口&#xff0c;比如开放的端口、使用的CDN、是否有泄露的源码&#xff09; 一、收集信息 是了解一个web服务的首要途径&#xff0c;可以通过htt…

混沌工程遇上AI:智能化系统韧性测试的前沿实践

#作者&#xff1a;曹付江 文章目录 1、什么是AI驱动的混沌工程&#xff1f;2、AI与混沌工程结合的价值3、技术实现3.1 AI模型开发3.1.1模型选择与构建3.1.2模型训练3.1.3 模型验证与调参3.1.4 模型测试3.1.5 知识库建设与持续学习 4、混沌工程与AI实践结合4.1 利用AI从运维专家…

Redis的线程模型

Redis 的单线程模型详解 Redis 的“单线程”模型主要指的是其 主线程&#xff0c;这个主线程负责从客户端接收请求、解析命令、处理数据和返回响应。为了深入了解 Redis 单线程的具体工作流程&#xff0c;我们可以将其分为以下几个步骤&#xff1a; 接收客户端请求 Redis 的主线…

《深度学习神经网络:颠覆生活的魔法科技与未来发展新航向》

深度学习神经网络对我们生活的影响 一、医疗领域 深度学习神经网络在医疗领域的应用可谓意义重大。在疾病诊断方面&#xff0c;它能够精准分析医疗影像&#xff0c;如通过对大量的 CT、MRI 图像进行深度学习&#xff0c;快速准确地识别出微小的肿瘤病变&#xff0c;为医生提供…

YOLOv11融合特征细化前馈网络 FRFN[CVPR2024]及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 一、 模块介绍 论文链接&#xff1a;Adapt or Rerish 代码链接&#xff1a;https://github.com/joshyZhou/AST 论文速览&#xff1a;基于 transformer 的方法在图像恢复任务中取得了有希望的性能&#xff0c;因为…

K8S简单部署,以及UI界面配置

准备两台服务器K8Smaster和K8Sminion 分别在两台服务器上执行以下代码 #添加hosts解析&#xff1b; cat >/etc/hosts<<EOF 127.0.0.1 localhost localhost.localdomain 192.168.45.133 master1 192.168.45.135 node2 EOF #临时关闭selinux和防火墙&#xff1b; sed …

vue3框架还需要学习什么

一.vue3框架相比较vue2框架有了什么改变&#xff1f; 1.composition api(组合式api) Vue 2&#xff1a;基于 Options API&#xff0c;组件的逻辑和状态被拆分到不同的选项中&#xff0c;如 data、methods、computed、watch 等&#xff0c;可能导致复杂组件中的逻辑分散&#xf…

爬虫 - 二手交易电商平台数据采集 (一)

背景: 近期有一个需求需要采集某电商网站平台的商品数据进行分析。因此&#xff0c;我计划先用Python实现一个简单的版本&#xff0c;以快速测试技术的实现可能性&#xff0c;再用PHP实现一个更完整的版本。文章中涉及的技术仅为学习和测试用途&#xff0c;请勿用于商业或非法用…

Chrome与傲游浏览器性能与功能的深度对比

在当今数字化时代&#xff0c;浏览器作为我们日常上网冲浪、工作学习的重要工具&#xff0c;其性能与功能直接影响着我们的使用体验。本文将对Chrome和傲游两款主流浏览器进行深度对比&#xff0c;帮助用户更好地了解它们的差异&#xff0c;以便做出更合适的选择。&#xff08;…

Docker Compose部署Rabbitmq(延迟插件已下载)

整个工具的代码都在Gitee或者Github地址内 gitee&#xff1a;solomon-parent: 这个项目主要是总结了工作上遇到的问题以及学习一些框架用于整合例如:rabbitMq、reids、Mqtt、S3协议的文件服务器、mongodb github&#xff1a;GitHub - ZeroNing/solomon-parent: 这个项目主要是…