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

目录:

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

前言

打怪升级:第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.mfbz.cn/a/14894.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WinScope实现录制视频与是Timeline时间轴同步设置方法-千里马framework车载手机系统开发实战

hi&#xff0c;粉丝朋友们&#xff01; 背景&#xff1a; 今天来分享一个粉丝朋友提出的问题&#xff0c;那就是他在学习wms课程时候有用到winscope工具&#xff0c;提出一个疑问&#xff0c;就是google官网说的有录屏可以结合起来一起看。具体如下&#xff1a; 其实这个以…

小案例CSS

代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible" content"IEedge"> <meta name"viewport" content"widthde…

什么是LVS

&#x1f618;作者简介&#xff1a;一名99年运维岗位员工。&#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。&#x1f64f;创作不易&#xff0c;动动…

零售新时代,零售行业数字化破局的新路径

深夜11点&#xff0c;门店店长小张还在加班&#xff0c;因为小张还需要盘点今日销售额、库存等信息&#xff0c;这些整理好的数据需要手动录入至总公司的系统中。 多门店的零售行业中&#xff0c;这是他们每天的工作日常&#xff1a;门店先通过excel做手工报表&#xff0c;再把…

PowerShell批量修改、替换大量文件的文件名

本文介绍基于PowerShell语言&#xff0c;对文件夹中全部文件的名称加以批量替换、修改的方法。 在之前的文章基于Python一次性批量修改多个文件的文件名&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/115869725&#xff09;中&#xff0c;我们介绍了基…

Zynq-7000、FMQL45T900的GPIO控制(七)---linux驱动层配置GPIO中断输入

本文使用的驱动代码 (1条消息) FMQL45T900linux驱动外部中断输入ZYNQ-7000linux驱动外部中断输入资源-CSDN文库 在Zynq-7000、FMQL45T900驱动层也时常会用到对GPIO的控制&#xff0c;这里就针对实际使用的情况进行说明&#xff0c;首先根据之前的帖子确实使用GPIO编号 这里采…

VAE 理论推导及代码实现

VAE 理论推导及代码实现 熵、交叉熵、KL 散度的概念 熵&#xff08;Entropy) 假设 p (x&#xff09;是一个分布函数&#xff0c;满足在 x 上的积分为 1&#xff0c;那么 p ( x ) p(x) p(x)的熵定义为 H ( p ( x ) ) H (p (x)) H(p(x))&#xff0c;这里我们简写为 H ( p )…

干货!ICLR 2023 | 更稳定高效的因果发现方法-自适应加权

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; ╱ 个人简介╱ 张岸 新加坡国立大学NExT实验室博士后&#xff0c;主要研究Robust & Trustable AI。 个人主页&#xff1a;https://anzhang314.github.io/ 01 内容简介 可微分的因果发现方法&#xff0c;是从…

input 各类事件汇总触发时机触发顺序

今天梳理了一下input框的各类事件&#xff0c;简单介绍一下吧 目录 1.click 2.focus 3.blur 4.change 5.input 6.keydown 7.keyup 8.select 1.click 点击事件&#xff0c;简单易理解&#xff0c;点击触发&#xff0c;等下跟focus事件一起比较 2.focus 获取焦点事件…

每日学术速递4.24

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Collaborative Diffusion for Multi-Modal Face Generation and Editing(CVPR 2023) 标题&#xff1a;多模态人脸生成和编辑的协同扩散 作者&#xff1a;Ziqi Huang, Kelvin C.K. …

RabbitMQ之发布确认

1. 发布确认原理 ​ 生产者将信道设置成 confirm 模式&#xff0c;一旦信道进入 confirm 模式&#xff0c;所有在该信道上面发布的 消息都将会被指派一个唯一的 ID(从 1 开始)&#xff0c;一旦消息被投递到所有匹配的队列之后&#xff0c;broker 就会发送一个确认给生产者(包含…

Hibernate多表关联——(一对多关系)

Hibernate多表关联——&#xff08;一对多关系&#xff09; 文章目录 Hibernate多表关联——&#xff08;一对多关系&#xff09;1.分别在类中添加属性&#xff1a;2.hibernate建表3.使用测试类在表中添加数据 hibernate是连接数据库使得更容易操作数据库数据的一个框架&#x…

ASEMI代理亚德诺AD8130ARZ-REEL7芯片应用与参数分析

编辑-Z 本文将对AD8130ARZ-REEL7芯片进行详细的应用与参数分析&#xff0c;包括其主要特征、接口定义、电气特性以及使用注意事项等方面&#xff0c;旨在为广大读者提供对该芯片更全面的了解。 1、主要特征 AD8130ARZ-REEL7芯片是一种用于高速、低功耗差分信号放大的电路&…

R语言 | 因子

目录 一、使用factor()函数或as.factor()函数建立因子 二、指定缺失的Levels值 三、labels参数 四、因子的转换 五、数值型因子转换时常见的错误 六、再看levels参数 七、有序因子 八、table()函数 九、认识系统内建的数据集 在类别数据中&#xff0c;有些数据是可以排序…

使用binding时,LayoutSubscribeFragmentBinding报错

LayoutRecommendFragmentBinding是一个DataBinding类&#xff0c;它由编译器自动生成&#xff0c;用于访问布局文件中的视图。如果你在代码中看到LayoutRecommendFragmentBinding报红&#xff08;提示未解析的引用&#xff09;&#xff0c;可能有以下原因&#xff1a; 1. 检查…

软件工程开发文档写作教程(04)—开发文档的编制策略

本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl本文参考资料&#xff1a;电子工业出版社《软件文档写作教程》 马平&#xff0c;黄冬梅编著 开发文档编制策略 文档策略是由上级(资深)管理者制订的&#xff0c;对下级开发单位或开发人…

【C++ Metaprogramming】0. 在C++中实现类似C#的泛型类

两年前&#xff0c;笔者因为项目原因刚开始接触C&#xff0c;当时就在想&#xff0c;如果C有类似C#中的泛型限定就好了&#xff0c;能让代码简单许多。我也一度认为&#xff1a; 虽然C有模板类&#xff0c;但是却没办法实现C#中泛型特有的 where 关键词&#xff1a; public c…

胜叔说SI_PI_EMC

第一课 分享的目的 书籍推荐 第二课 什么是理论分析 仿真不是目的&#xff0c;仿真是验证理论分析的方法 测试不是目的&#xff0c;测试是验证理论分析的方法 第三课 信号完整性简介 小型化、高功率、高密度 传输线理论&#xff1a;传输线是由 信号路径和返回路径共同组…

OSI七层模型、TCP/IP四层模型

OSI七层模型和TCP/IP四层模型 OSI七层模型 物理层&#xff1a;底层数据传输&#xff0c;如网线、网卡标准数据链路层&#xff1a;定义数据基本格式&#xff0c;如何传输如何标识&#xff1b;如网卡MAC地址网络层&#xff1a;定义IP地址&#xff0c;定义路由功能&#xff1b;如…

温度调制式差示扫描量热法(MTDSC)中的正弦波温度控制技术

摘要&#xff1a;在调制温度式差式扫描量热仪&#xff08;MTDSC&#xff09;中&#xff0c;关键技术之一是正弦波加热温度的实现&#xff0c;此技术是制约目前国内无法生产MTDSC量热仪的重要障碍&#xff0c;这主要是因为现有的PID温控技术根本无法实现不同幅值和频率正弦波这样…