【难学易用c++ 之 继承】

news2025/1/10 12:55:06

目录:

  • 前言
  • 一、继承的概念及定义
    • (一)概念
    • (二)继承定义
      • 继承关系和访问限定符
      • 继承基类成员访问方式的变化
  • 二、基类和派生类对象赋值转换
  • 三、继承中的作用域
  • 四、派生类的默认成员函数
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、复杂的菱形继承及菱形虚拟继承
    • 菱形继承
    • 虚继承
    • 组合
  • 补充

前言

打怪升级:第50天
在这里插入图片描述

一、继承的概念及定义

(一)概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,他允许类在保持原有特性的基础上进行拓展,增加新的功能,这样产生的类叫做子类或者派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

示例:

class Person
{

public:
	void Print()
	{
		cout << "name : " << _name << endl;
		cout << "age : " << _age << endl;
	}
protected:
	string _name = "小李子";
	int _age = 20;
};

//  Person类作为父类,被student 和 teacher继承后,父类的public 和 protected 成员(变量和函数)都会变成子类的一部分,可以在子类中进行访问。
class Student : public Person
{
private:
	string _s_id = "000"; // 学号
};

class Teacher : public Person
{
private:
	string _t_id = "100"; // 工号
};

void Test_person1()
{
	Student s1;
	s1.Print();
	Teacher t1;
	t1.Print();
}

运行实例:
在这里插入图片描述


(二)继承定义

在这里插入图片描述

继承关系和访问限定符

在这里插入图片描述

继承基类成员访问方式的变化

这里是引用

基类的私有成员(private)子类无论如何都是不可见的,基类的保护成员(protected)和共有成员(public)在子类中的受继承方式限制,一般都使用 public继承,也就都是可见的;
这就好比父亲挣得钱和 父亲藏的私房钱,父亲挣的钱一家人都可以用,但是私房钱只有父亲自己可以用,哪怕是儿子也不行。

这里有一个小问题:子类可以访问到父类的私有成员(私房钱)吗,
我们上面讲:父类的私有成员在子类中不可见,既然不可见(不知道老爸私房钱藏在哪里),那是不是就不能使用?

在这里插入图片描述

  • 总结
  1. 基类private成员在派生类中无论以何种方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类不管来类内还是类外都无法访问到
  2. 基类private成员在派生类中是访问不到的,如果想要在派生类中可以访问,但是在类外不能访问到,就需要使用protected。可以看出:保护成员限定符是因继承才出现的(c++刚问世的时候只设置了私有和共有,在C++2.0时才加入了保护成员)。
  3. 基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private
  4. 使用关键字class时默认的访问和继承方式是private, 使用struct 时默认的访问和继承方式是public,不过最好显示写出继承方式
  5. 在实际运用中一般使用都是public继承,几乎很少使用 protected/private继承。
  6. 一句话总结:权限可以缩小但不可放大(类比:const变量不能被非const变量引用)。

二、基类和派生类对象赋值转换

  • 派生类可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法加切片或者切割
    寓意把派生类中基类那部分切出来赋值过去。

  • 基类对象不能赋值给派生类对象

  • 基类的指针或引用可以通过强制类型转换赋值给派生类的指针或引用。但是只有访问基类成员时才是安全的,否则可能造成越界

这里是引用在这里插入图片描述在这里插入图片描述


三、继承中的作用域

  1. 在继承体系中,基类和派生类都有独立的作用域
  2. 子类和父类中有同名成员子类成员将屏蔽对父类同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类对象中,可以使用 基类::基类成员显示访问)。
  3. 注意:成员函数只要函数名相同就构成隐藏。(区分函数重载:在同一作用域内,函数名相同而参数不同);
  4. 注意在实际的继承体系里面最好不要定义同名的成员。
class Person
{
public:
	void Print()
	{
		cout << "name : " << _name << endl;
		cout << "age : " << _age << endl;
	}

	string _name = "小李子";
	int _age = 20;
};

class Student : public Person
{
public:
	void Print()
	{
		cout << "id :" << _s_id << endl;
	}

	string _s_id = "000"; // 学号
};

class Teacher : public Person
{
public:
	void Print()
	{
		cout << "id :" << _t_id << endl;
	}

	string _t_id = "100"; // 工号
};

void Test_Person3()
{
	Student s1;
	s1.Person::Print();  // 隐藏,显示调用同名成员
	s1.Print();
	cout << endl;
	
	Teacher  t1;
	t1.Person::Print();
	t1.Print();
}

这里是引用


四、派生类的默认成员函数

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类
中,这几个成员函数是如何生成的呢?

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
    的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
    保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构。(保证进栈出栈顺序)。
  7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

这里是引用在这里插入图片描述

class Person
{
public:
	Person(string name = "小李子", int age = 20)
		:_name(name)
		, _age(age)
	{}
	void Print()
	{
		cout << "name : " << _name << endl;
		cout << "age : " << _age << endl;
	}

	string _name;
	int _age;
};

class Student : public Person
{
public:
	Student(string name = "小李子", int age = 20, string id = "000")
		:Person(name, age)  // 显示调用基类的构造
		, _s_id(id)
	{}

	Student(const Student& s)
		:Person(s)           //  显示调用
		, _s_id(s._s_id)
	{}
	void Print()
	{
		Person::Print();
		cout << "id :" << _s_id << endl;
		cout << endl;
	}

	string _s_id; // 学号
};


void Test_Person4()
{
	Student s1;
	s1.Print();
	Student s2("陈平安", 40);
	s2.Print();
	Student s3("裴钱", 26, "001");
	s3.Print();

	Student s4 = s3;
	s4.Print();
}

这里是引用

class Base
{
public:
	Base(int val = 10)
		:b_val(val)
	{
		cout << "Base()" << endl;
	}

	Base(const Base& b)
		:b_val(b.b_val)
	{
		cout << "Base(const Base&)" << endl;
	}

	Base& operator=(const Base& b)
	{
		if (&b != this)
		{
			b_val = b.b_val;
			cout << "Base::operator=()" << endl;
		}

		return *this;
	}

	~Base() { cout << "~Base()" << endl; }

	int b_val;
};

class Son: public Base
{
public:
	Son(int val1 = 10, int val2 = 20)
		:Base(val1)
		,s_val(val2)
	{
		cout << "Son()" << endl;
	}

	Son(const Son& b) 
		:Base(b)  //  派生类对象初始化基类对象
		,s_val(b.s_val)
	{
		cout << "Son(const Base&)" << endl;
	}

	Son& operator=(const Son& s)
	{
		if (&s != this)
		{
			Base::operator=(s);  // 显示调用基类的赋值
			s_val = s.s_val;
			cout << "Son::operator=()" << endl;
		}

		return *this;
	}

	~Son() { cout << "~Son()" << endl; }

	int s_val;
};

void Test_p5()
{
	Son s1;
	cout << endl;

	Son s2 = s1;
	cout << endl;

	Son s3;
	s3 = s1;
	cout << endl;

	Son s4(1, 2);
	cout << endl;
}

这里是引用

注意:在派生类的构造和赋值中 如果我们不显示调用基类的赋值和拷贝构造,编译器不会自动调用


五、继承与友元

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

class Student;
class Person
{
public:
 friend void Display(const Person& p, const Student& s);
protected:
 string _name; // 姓名
};
class Student : public Person
{
protected:
 int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
 cout << p._name << endl;
 cout << s._stuNum << endl;
}
void main()
{
 Person p;
 Student s;
 Display(p, s);
}

这里是引用


六、继承与静态成员

**基类定义了static静态成员,则整个继承体系里面只有这样一个静态成员。**无论派生出多少个子类,都只有一个static成员实例。

class Base
{
public:
	static int cnt;
};
int Base::cnt = 0;   // 静态成员变量需要在类外初始化,且初始化时不需要加static

class Son1 :public Base
{
public:
	Son1()
	{
		++cnt;
	}
};

class Son2 :public Base
{
public:
	Son2()
	{
		++cnt;
	}
};

void Test_Static1()
{
	Son1 s1_1;
	Son1 s1_2;
	cout << "cnt =  " << s1_2.cnt << endl;
	Son2 s2_1;
	Son2 s2_2;
	cout << "cnt =  " << s2_2.cnt << endl;
}

这里是引用


七、复杂的菱形继承及菱形虚拟继承

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

菱形继承

菱形继承造成的影响:Child从父类Student中继承了Person类的属性,又从父类Teacher中也继承了Person类的属性,那么此时,Child类中就有了两份Person类属性,
这会有两个问题:二义性和数据冗余

  1. 我们访问Person类属性时不能确定访问的到底是从父类STudent中继承下来的还是从父类Teacher中继承下来的;
  2. 多份的Person类属性会造成数据的冗余。

这里是引用

虚继承

为了解决菱形继承带来的问题,我们祖师爷在C++3.0给出了解决方案:虚继承
关键字:virtual(虚拟)

class A
{
public:
	int _a;
};

class B : virtual public A  //  虚继承
{
public:
	int _b;
};

class C : virtual public A  //  虚继承
{ 
public:
	int _c;
};

class D : public B, public C 
{
public:
	int _d;
};

void Test_1()
{
	D d1;
	d1.B::_a = 10;
	d1.C::_a = 30;
	
}

这里是引用在这里插入图片描述在这里插入图片描述

这里有盆友可能会提出疑问:我们解决数据冗余不就是为了节省空间吗,这里多增加了一个A,而且父类中的A也还存在,并且A中存放的地址还指向另外一块区域,这样不就更加浪费空间了吗,难道我们的祖师爷在当时设计虚继承时“喝了假酒”,所以这部分写出问题来啦?

这里是引用在这里插入图片描述

虚继承原理解析:
在这里插入图片描述

很多人说c++的语法复杂,多继承就是一个体现:有了多继承就会有菱形继承,有了菱形继承就会有菱形虚拟继承,底层实现就很复杂,
并且编译器在背后为我们做的一系列操作也是有时间消耗的,所以一般不建议写出多继承,一定不要写菱形继承!
由于c++现世的时间非常早,当时的参考资料较少,因此设计上难免会有一些不合理的地方(如多继承、string类等等),我们的前辈也在对c++不断的进行缝缝补补,后世的一些OO语言在参考了c++之后就限制了继承的操作,如Java就只有单继承没有多继承。

组合

class A
{
public:
	int _a1;
protected:
	int _a2;
};

class B :public A
{
public:
	void Init()
	{
		_a1 = 10;
		_a2 = 20;
	}
};

class C
{
public:
	void Init()
	{
		_aa._a1 = 10;
//		_aa._a2 = 20;   //  不可访问
	}

	A _aa;      //   组合 -- c中有a
};

在这里插入图片描述在这里插入图片描述


补充

在这里插入图片描述在这里插入图片描述

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

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

相关文章

TiDB实战篇-常用的高可用架构

简介 TiDB实战篇-常用的高可用架构。 高可用要考虑的问题 同城三中心 RTO<35秒 RPO0(因为一个数据中心挂点了&#xff0c;还有其他两个可以提供服务) (优点)数据副本不能在同一个数据中心&#xff08;raft多数存活&#xff09;&#xff08;PD的label标签能够解决这个问题…

OpenCV基础之常见的图像梯度算子

文章目录 OpenCV基础之常见的图像梯度Roberts交叉算子Prewitt算子Sobel算子Laplacian算子 OpenCV基础之常见的图像梯度 梯度是一个向量&#xff0c;梯度方向指向函数变化最快的方向&#xff0c;大小就是它的模&#xff0c;也是最大的变化率。 图像梯度是指在图像中某个位置处沿…

关于容器(Docker)的形象比喻

1 将容器比喻为样板间 容器是一种特殊的进程 容器依赖与Linux操作系统内核的几项技术&#xff1a;namespace、cgroup、chroot namespace 与编程语言里的 namespace 有点类似&#xff0c;它可以创建出独立的文件系统、主机名、进程号、网络等资源空间&#xff0c;相当于给进程…

回归问题(Regression)

Regression 前言Dependent vs. Explanatory VariablesHandle Numerical Labelssquared error和variance什么区别 Linear RegressionLinear Regression in 1 Dimension Least Squares &#xff08;最小二乘&#xff0c;重点&#xff09;Least Squares ObjectiveMinimizing a Dif…

确保软件项目成功——验收测试指南

确保软件项目成功——验收测试指南 在软件项目验收测试中&#xff0c;软件测试报告是非常重要的一部分&#xff0c;需要准备和提交。以山东省在2021印发的《政府采购履约验收管理办法》为例&#xff1a; 省级各国家机关、事业单位和团体组织&#xff08;以下统称“采购人”&…

会话跟踪——JWT令牌

会话指的是浏览器与服务器之间的一次连接&#xff0c;我们称之为一次会话。 在用户打开浏览器第一个访问服务器的时候&#xff0c;这个会话就建立了&#xff0c;只要有任何一方断开连接&#xff0c;此时会话就结束了。再一次会话中是可以包含多次请求和相应。那什么是会话跟踪呢…

动力节点springsecurity笔记14~18SpringSecurity 集成thymeleaf

15 SpringSecurity 集成thymeleaf 此项目是在springsecurity-12-database-authorization-method 的基础上进行 复制springsecurity-12-database-authorization-method 并重命名为springsecurity-13-thymeleaf 15.1 添加thymeleaf依赖 | <groupId>org.springframewor…

vue3+vite3+typescript使用wangEditor编辑器

文章目录 ⭐写在前面⭐步入正题&#x1f680;1.安装&#x1f680;2.配置2.1 存数据2.2 读数据 &#x1f680;3.跨域及其他问题3.1 跨域3.2 其他问题 &#x1f680;4.写在最后 ⭐写在前面 &#x1f680; 框架Vue3 Vite3 TypeScript&#xff1a; &#x1f449; Vue3&#xff…

地铁站人流检测硬件部分

目录 一、概述 二、驱动程序 2.1debug串口 2.2体重传感器HX711 2.3滴答定时器 2.4ESP8266 2.5人体检测 2.6 IIC的GPIO 2.7 OLED的IIC 2.8 LED 三、应用 四、中断 一、概述 使用STM32C8T6作为主控 A9 ---> tx&#xff08;调试串口&#xff09; A10 ---> …

算法训练 Day41 | 动态规划

343. 整数拆分 思路&#xff1a; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i]&#xff1a;分拆数字i&#xff0c;可以得到的最大乘积为dp[i]。 确定递推公式&#xff1a;dp[i] max(dp[i], max((i - j) * j, dp[i - j] * j)) 可以想 dp[i]最…

【python装饰器:看懂这10个例子你就掌握了!】

基本说明 Python 装饰器是一种函数&#xff0c;它可以用来修改其他函数的功能。它是 Python 中的一项高级编程技术&#xff0c;也是 Python 中比较重要的语法之一。 简单来说&#xff0c;装饰器就是一个函数&#xff0c;它可以接受一个函数作为参数&#xff0c;并返回一个函数…

Obsidian+坚果云+FolderSync解决电脑端和安卓端同步方案

目录1.Obsidian电脑端准备 2.Obsidian安卓端准备 3.坚果云电脑端准备 4.坚果云手机端准备 5.FolderSync手机端准备 6.百度云冗余备份 1.Obsidian电脑端准备 这里以windows版本为例&#xff0c;下载后安装 1.Obsidian官网&#xff1a;https://obsidian.md/ 官网下载有时候…

电力电网行业IT运维方案

智能电网背景下&#xff0c;电力、电网企业信息化逐渐渗透到其业务链的各个环节&#xff0c;云计算、物联网、移动互联网等新技术的应用&#xff0c;更驱动信息化与业务创新深度融合。电力、电网企业集团信息系统群逐渐朝着一体化方向发展&#xff0c;信息链越来越长&#xff0…

银行数字化转型导师坚鹏:宏观经济趋势与资本行业机遇和挑战

2023年宏观经济趋势与资本行业机遇和挑战 课程背景&#xff1a; 很多学员存在以下问题&#xff1a; 不知道我国目前的宏观经济形势&#xff1f; 不清楚宏观环境对我国经济的影响&#xff1f; 不知道资本行业未来主要发展趋势&#xff1f; 课程特色&#xff1a; 精彩解…

基于php的校园校园兼职网站的设计与实现

摘要 近年来&#xff0c;信息技术在大学校园中得到了广泛的应用&#xff0c;主要体现在两个方面&#xff1a;一是学校管理系统&#xff0c;包括教务管理、行政管理和分校管理&#xff0c;是我国大学管理和信息传递的主要渠道。二是学生生活服务平台。而随着大学生毕业人数的年…

leetcode重点题目分类别记录(四)图论深入

文章目录 入度出度最大网络秩可以到达所有点的最少点数目 并查集省份数量等式方程的可满足性按字典序排列最小的等效字符串以图判树 二分图判断二分图 深度优先搜索封闭岛屿数量太平洋大西洋水流问题 广度优先搜索树上逃逸最短路径多源最短路径 拓扑排序DFS解决拓扑排序BFS解决…

MIPS指令集-mars-cpu

MIPS通用寄存器 MIPS有32个通用寄存器&#xff08;$0-$31&#xff09;&#xff0c;各寄存器的功能及汇编程序中使用约定如下&#xff1a; 下表描述32个通用寄存器的别名和用途 REGISTER NAME USAGE $0 $zero 常量0(constant value 0) $1 $at 保留给汇编器(Reserved f…

K近邻算法(手写代码+图像识别实践)

k近邻算法作为一个分类算法&#xff0c;他通过计算不同特征值之间的距离来进行分类&#xff0c;它的工作原理是存在一个样本集合作为训练样本集&#xff0c;且每个样本都存在一个标签&#xff0c;此时&#xff0c;输入一个新的样本不存在标签&#xff0c;我们通过计算这个新样本…

【Android车载系列】第10章 系统服务-SystemServer源码分析(API28)

1 SystemServer启动 &emps;&emps;SystemServer进程启动&#xff0c;首先从SystemServer.java文件的main()方法开始。 290 /** 291 * The main entry point from zygote. 292 */ 293 public static void main(String[] args) { 294 new SystemSe…

S32K3系列单片机开发笔记(SIUL是什么/配置引脚复用的功能·)

前言 今天花时间看了一下&#xff0c;SIUL2模块的相关内容&#xff0c;并参照文档&#xff0c;以及例程作了一些小记录&#xff0c;知道该如何使用这个外设&#xff0c;包括引脚的配置&#xff0c;中断配置&#xff0c;以及常用函数的使用等&#xff0c;但对其中的一些细节还需…