【C++】继承基础知识一遍过

news2024/11/28 10:55:00

目录

一,概念

二,继承定义

1. 继承格式 

2. 访问限定符与继承方式的关系

3. 继承父类成员访问方式的变化

小结: 

三. 父类与子类对象赋值转化

四,继承作用域

1.特点 

2. 测试题

五,派生类不一样的默认成员函数

1.构造函数

2.拷贝构造

3.赋值符号重载

4.析构函数

5. 小结

六,友元与继承

七,继承与静态成员

2.思考:如何制作一个无法被继承的类

八,菱形继承与虚拟菱形继承

1. 菱形继承的问题

2. 虚拟继承解决方案

3. 虚拟继承底层细节 

九,继承与组合

总结:

结语


一,概念

继承(inheritance)机制是面向对象程序设计 使代码可以复用的最重要的手段,它允许程序员在 保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用, 继承是类设计层次的复用

基于原有类的一般叫做基类(base_class),我感觉叫他为—— 父类 ,这更容易理解。
运行下面代码
#include <iostream>
#include <string>

using namespace std;

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18;
};

class Student : public Person
{
protected:
	int _stuid; // 学号
};

class Teacher : public Person
{
protected:
	int _jobid; // 工号
};

int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	t.Print();
	return 0;
}

继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。调用Print可以看到成员函数的复用

二,继承定义

1. 继承格式 

下面我们看到Person是父类,也称作基类。 Student是子类,也称作派生类  

2. 访问限定符与继承方式的关系

 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public不过最好显示的写出继承方式。

3. 继承父类成员访问方式的变化

小结: 

1. 父类private成员在派生类中无论以什么方式继承都是不可见的。这里的 不可见是指父类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
2. 父类private成员在派生类中虽然被继承了,但父类私有是不可见的(因为权限问题),如果父类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。 可以看出保护成员限定符是因继承才出现的

 3. 子类访问权限 =   修饰符与继承方式的最小权限 ,如:修饰符是private,继承方式是public最终权限是private。 权限从大到小是:public > protected > private

4.在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。所以基本上,常用的处理 继承方式:public 处理成员变量,函数: 用public / protected

三. 父类与子类对象赋值转化

子类对象 可以赋值给 父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫 切片或者 切割。寓意把派生类中父类那部分切来赋值过去。
class Person
{
protected:
	string _name; // 姓名
	string _sex;
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};

void Test()
{
	Student sobj;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;

	//2.父类对象不能赋值给子类对象
	// sobj = pobj;


	// 3.父类的指针可以通过强制类型转换赋值给子类的指针
	pp = &sobj;
	Student * ps1 = (Student*)pp; // 这种情况转换时可以的。
	ps1->_No = 10;

	// 父类利用指针强转给子类指针
	pp = &pobj;
	Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
	ps2->_No = 10;   
}

我们可以发现:

1. 父类对象不能赋值给子类对象

2. 父类对象地址强转为子类地址,最终在子类访问其新成员时会发生越界报错。

四,继承作用域

1.特点 

1. 在继承体系中 基类派生类都有 独立的作用域。(所以即使父子类有同名函数,因不在同一作用域则不叫做函数重载)
2. 子类和父类中有同名成员, 子类成员将屏蔽父类对同名成员的直接访问,这种情况叫 隐藏 也叫重定义。(在子类中,可以 使用 基类::基类成员显示访问父类成员
3. 需要注意的是如果是成员函数的隐藏,只需要 函数名相同就构成隐藏
4. 注意在实际中在 继承体系里面最好 不要定义同名的成员

2. 测试题

class A
{
public:
 void fun(int i)
 {
 cout << "func()" << endl;
 }
};

class B : public A
{
public:
 void fun(int i)
 {
 A::fun();
 cout << "func(int i)->" << i << endl;
 }
};

void Test()
{
 B b;
 b.fun();
};
// 不定向选择:
// A: 子父func构成重载
// B: 子父func构成隐藏
// C: 程序报错
// D:以上都不对

答案:BC 。B,首先是子父类拥有同名成员函数,构成隐藏。C,因为子类对父类同名函数的隐藏,导致无法找到匹配fun函数,因此报错。

五,派生类不一样的默认成员函数

 这是本次的实验代码,经过这次的学习,我们来补充这个派生类。

class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{
			cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	// 构造函数
protected:
	int _num; //学号
};

void Test()
{
	Student a;
	int x = 1;
}

int main()
{
	Test();
	return 0;
}

1.构造函数

 我们先不写构造函数,我们查看默认构造函数会发生什么事?

 很明显:默认构造函数,父类会调用其自己的构造函数;子类也是调用其自己的构造函数,没有,则编译器自动生成构造函数。(总之:各自构造各自的)且构造顺序为先父类——>子类。

    // 构造函数
	Student(const char* name ,const int num = 0)
		:Person(name)
		,_num(num)
	{}

2.拷贝构造

 思路:父类数据,通过父类的隐式转化。

    // 拷贝构造
	Student(const Student& s1)
		:_num(s1._num)
		, Person(s1)    // 通过子类向父类的强转化
	{
		cout << "Student(const Student& )" << endl;
	}

3.赋值符号重载

 思路:调用父类赋值符号重载,再给子类成员变量赋值。

    // 赋值符号重载
	Student& operator=(const Student& s1)
	{
		cout << "operator=" << endl;
		if (this != &s1)
		{
			Person::operator=(s1);
			_num = s1._num;
		}	
		return *this;
	}

 补充:如果子类成员变量不需要深拷贝,其实不需要写拷贝,赋值符号重载函数,因为父类有这两个函数。

4.析构函数

 这个没啥好说的,父子类各自调用各自的析构函数。

析构顺序:子类先析构,当子类析构完成后再调用父类析构。

5. 小结

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

六,友元与继承

如果大家对友元陌生了,可以看小博主这篇文章回忆:详解C++类和对象(下篇)——用代码实践功能_花果山~~程序猿的博客-CSDN博客

结论:派生类不能继承父类的友元关系,换句话说,友元函数或者友元类无法直接获取派生类的私有或者保护成员变量及函数。  

示例代码: 

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);
}

友元关系无法继承,所以在派生类中如果需要友元关系,需要重新声明友元关系。 

七,继承与静态成员

静态成员变量与函数相关基础知识详解C++类和对象(下篇)——用代码实践功能_花果山~~程序猿的博客-CSDN博客

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

示例代码:

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};

int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

class Graduate : public Student
{
protected:
 string _seminarCourse; // 研究科目
};

void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " Person接口 人数 :" << Person::_count << endl;
	Student::_count = 666; // 在Student类,访问Person类中静态成员变量,并且设置该静态成员变量
	cout << " Person接口 人数 :" << Person::_count << endl;
	cout << " Student接口 人数 :" << Student::_count << endl;
	cout << " Graduate接口 人数 :" << Graduate::_count << endl;
}

2.思考:如何制作一个无法被继承的类

 思路:对构造与析构函数其中一个私有化。

class  Person
{
public:
	~Person()	
	{}
private:
	Person()
	{}
    
	int _age = -1;
	char* _name;
};

class Student : public Person
{
private:
	int _score;
};


void TestPerson()
{
	Student a;
    Person  a1; // 当然,父类自己也实例不了对象了
}

诺,这样子类就调不动父类。但,同时父类自己也实例不了对象,你真的,哭死我了。

解决方案:创建一个静态成员函数,让类外就可以实例化对象,这样就绕开了创建对象时系统调用构造函数这条路。

    // 单纯的成员函数也不行,因为对象都没有你告诉咋调函数。
	static Person create_Person()
	{
		return Person();
	}

八,菱形继承与虚拟菱形继承


1. 菱形继承的问题

从下面的对象成员模型构造,可以看出菱形继承有 数据冗余 二义性 的问题。 Assistant 的对象中 Person 成员会有两份。这会
导致内存浪费!!!

2. 虚拟继承解决方案

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在 Student Teacher 的继承 Person 时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地 方去使用。

实验代码:

class A
{
public:
	int _a;
};

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

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

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

int main()
{
	D d;
	d.B::_a = 1;
	cout << &(d.B::_a) << endl;

	d.C::_a = 2;
	cout << &(d.C::_a) << endl;

	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

虚拟菱形继承,让一个类通过虚拟继承从多个基类继承时,只会保留一个共同的基类子对象,避免了出现多个相同的基类子对象。

诺,数据冗余:

3. 虚拟继承底层细节 

 

这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?
这里是通过了 B C 的两个指针,指 向的一张表。这 两个指针 叫虚基表指针,这两个表叫 虚基表 。虚基表中存的偏移量。通过偏移量 可以找到下面的 A  

 这里会提到内存大小端知识,可以参见本文【C语言】整,浮点型数据存储,大小端。细节拉满!!_小端浮点数组_花果山~~程序猿的博客-CSDN博客

 原理精简图:

 补充:如果多个相同虚拟菱形继承类的对象,其访问的偏移表是同一套。如:D是一个虚拟菱形继承的类,则D   d1, d2, d3....., d1,d2,d3都是访问同一套偏移表。从这里我们也可以发现,虚拟菱形继承为了解决数据冗余和二义性的问题,需要访问偏移表,但毫无疑问,这会造成性能损失。

九,继承与组合

继承是一种关系,其中一个类(称为子类或派生类)可以继承另一个类(称为父类或基类)的属性和方法。

组合是另一种关系,其中一个类(称为容器类)包含另一个类的对象(称为成员类)。容器类通过创建成员类的对象来使用成员类的属性和方法。

(就像之前我们所学的STL中vector类这样的容器,里面放着string类)

总结:

1. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。 派生类和基类间的依赖关系很强,耦合度高。
2. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组 合类之间没有很强的依赖关系,耦合度低。优先使用对象组合 有助于你保持每个类被封装
3.实际尽量 多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要承。类之间的关系可以用继承, 可以用组合,就用组合

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

【nacos】2.1.1持续输出事件警告日志

nacos-server 2.1.1 持续输出事件警告日志,修复NamingTraceEvent连续打印日志。这是 2.1.1 beta 功能跟踪事件。如果没有订阅者处理TraceEvent&#xff0c;将打印此日志。2.1.1版本中忽略它&#xff0c;我们将在2.1.2版本中对其进行增强。 WARN There are no [com.alibaba.nac…

Linux系统中驱动之设备树的platform驱动实现

每日一个简单的驱动&#xff0c;日久方长&#xff0c;对Linux驱动就越来越熟悉&#xff0c;也越来容易学会写驱动程序。今日进行设备树下的platform设备驱动。 前面一篇我们讲解了传统的、未采用设备树的 platform 设备和驱动编写方法。最新的 Linux 内核已经支持了设备树&…

c语言练习45:模拟实现内存函数memcpy

模拟实现内存函数memcpy 针对内存块&#xff0c;不在乎内存中的数据。 拷贝内容有重叠的话应用memmove 模拟实现&#xff1a; 代码&#xff1a; 模拟实现memcpy #include<stdio.h> #include<assert.h> void* my_memcpy(void* dest, const void* src, size_t num…

【计算机基础知识3】IP 地址和子网掩码、DNS、HTTP

目录 前言 一、IP地址和子网掩码 1. IP地址的概念 2. IP地址的分类 3. 子网掩码的概念 4. 子网掩码的用途 二、域名系统&#xff08;DNS&#xff09; 1. DNS的作用 2. 域名解析过程 3. 如何配置和管理域名解析 三、HTTP&#xff08;超文本传输协议&#xff09; 1. H…

Pytest系列-测试用例前后置固件setup和teardown的介绍和使用(2)

简介 在unittest框架中&#xff0c;有两个前置方法&#xff0c;两个后置方法&#xff0c;还有两个模块方法&#xff0c;分别是 setup()&#xff1a;每个用例执行之前都会自动调用setupClass()&#xff1a;在类中所有的测试方法执行前会自动执行的代码&#xff0c;只执行一次t…

华为云中对象存储服务软件开发工具包(OBS SDK) C语言介绍

华为云的OBS介绍&#xff1a;摘自华为云官网&#xff1a;https://support.huaweicloud.com/obs/index.html 华为云的对象存储服务(Object Storage Service&#xff0c;OBS)是一个基于对象的海量存储服务&#xff0c;为客户提供海量、安全、高可靠、低成本的数据存储能力。 …

华为云云耀云服务器L实例评测|在Docker环境下部署Statping服务器监控工具

华为云云耀云服务器L实例评测&#xff5c;在Docker环境下部署Statping服务器监控工具 一、前言1.1 云耀云服务器L实例简介1.2 Statping简介1.3 Statping特点 二、本次实践介绍2.1 本次实践简介2.2 本次环境规划 三、购买云耀云服务器L实例3.1 购买云耀云服务器L实例3.3 查看云耀…

商汤科技AGI这些年:上半场「基建」,下半场「变现」

【潮汐商业评论/原创】 纵观一次次科技革命引领的生产力变革&#xff0c;都绝非一蹴而就&#xff0c;而是在不断的技术突破中&#xff0c;找到产业的落脚点&#xff0c;再回归到社会应用中去。 当下的人工智能也是如此。今年以来&#xff0c;大模型和生成式AI作为重要的科技突…

OpenCV项目实战(1)— 如何去截取视频中的帧

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。针对一段视频&#xff0c;如何去截取视频中的帧呢&#xff1f;本节课就给大家介绍两种方式&#xff0c;一种方式是按一定间隔来截取视频帧&#xff0c;另一种方式是截取视频的所有帧。希望大家学习之后能够有所收获&#x…

无涯教程-JavaScript - IMSIN函数

描述 IMSIN函数以x yi或x yj文本格式返回复数的正弦。复数的正弦为- $$\sin(x yi) \sin(x)\cosh(y) \cos(x)\sin(y)i $$ 语法 IMSIN (inumber)争论 Argument描述Required/OptionalInumberA Complex Number for which you want the sine.Required Notes Excel中的复数仅…

python-55-打包exe执行

目录 前言一、pyinstaller二、实践打包exe1、遇坑1&#xff1a;Plugin already registered2、遇坑2&#xff1a;OSError 句柄无效 三、总结 前言 你是否有这种烦恼&#xff1f; 别人在使用你的项目时可能还需要安装各种依赖包&#xff1f;别人在使用你的项目&#xff0c;可能…

Bean 的生命周期总结

目录 一、Bean生命周期的五个阶段 Bean的初始化 二、PostConstruct 和 PreDestroy 各自的效果 三、 实例化和初始化的区别 四、为什么要先设置属性在进⾏初始化呢&#xff1f; 一、Bean生命周期的五个阶段 Java 中的公共类称之为 Bean 或 Java Bean&#xff0c;而 Spring 中的…

深度学习的数值问题

文章目录 梯度下降临界点、驻点、拐点、鞍点、顶点&#xff08;曲线&#xff09;、曲率近似优化预测最佳步长 梯度下降 往斜率的反方向走。 临界点、驻点、拐点、鞍点、顶点&#xff08;曲线&#xff09;、曲率 临界点&#xff1a;在数学中&#xff0c;临界点是指函数的导数为…

【APISIX】W10安装APISIX

Apache APISIX 是一个动态、实时、高性能的云原生 API 网关&#xff0c;提供了负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。以下简单介绍Windows下借助Docker Desktop来安装APISIX。 具体应用场景可参考官网&#xff08;https://apisix.…

安科瑞铁塔基站能耗监控解决方案

安科瑞 华楠 1 背景概述 5G发展&#xff0c;基站先行。5G基站的选址建设&#xff0c;是保证5G信号覆盖的基础&#xff0c;因此5G基站建设是5G产业布局的一部分&#xff0c;也是5G成熟的基础。 2G、3G、4G均是低频段信号传输&#xff0c;宏基站几乎能应付所有的信号覆盖。但由…

Navicat Premium 16 安装及卸载

Navicat Premium 16 安装及卸载 文章目录 Navicat Premium 16 安装及卸载一 、简介二、下载三、安装四、使用五、卸载 一 、简介 Navicat Premium 是一套可创建多个连接的数据库开发工具&#xff0c;让你从单一应用程序中同时连接 MySQL、Redis、MariaDB、MongoDB、SQL Server、…

【HTML专栏4】常用标签(标题、段落、换行、文本格式化、注释及特殊字符)

本文属于HTML/CSS专栏文章&#xff0c;适合WEB前端开发入门学习&#xff0c;详细介绍HTML/CSS如果使用&#xff0c;如果对你有所帮助请一键三连支持&#xff0c;对博主系列文章感兴趣点击下方专栏了解详细。 博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;HTML/CS…

线程的常用方法及六种状态

一、线程核心执行流程方法 -- run&#xff08;&#xff09; run方法是Runnable接口中定义的。所有实现Runnable的接口的子类都需要覆写run方法。 run方法是线程的核心执行流程方法&#xff0c;也就是说&#xff0c;run方法决定了线程启动后要干什么&#xff0c;当run方法执行完…

2.11 PE结构:添加新的节区

在可执行PE文件中&#xff0c;节&#xff08;section&#xff09;是文件的组成部分之一&#xff0c;用于存储特定类型的数据。每个节都具有特定的作用和属性&#xff0c;通常来说一个正常的程序在被编译器创建后会生成一些固定的节&#xff0c;通过将数据组织在不同的节中&…

微信小程序接入隐私弹窗说明及详细过程

相信各位做小程序的小伙伴在微信小程序后台应该都接到了官方通知的小程序隐私新规&#xff0c;如果还未收到的小程序可以看一下&#xff1a; 为规范开发者的用户个人信息处理行为&#xff0c;保障用户合法权益&#xff0c;自2023年9月15日起&#xff0c;对于涉及处理用户个人信…