c++中继承方面的知识点

news2025/4/13 6:09:54

继承的概念及定义

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

例如下面的代码中就是使用了继承的语法知识。

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; // 工号
};

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

继承定义
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
在这里插入图片描述

继承关系和访问限定符
在这里插入图片描述
继承基类成员访问方式的变化

在这里插入图片描述
一共九种的访问方式,太多了,但是我们可以看到一个规律只要是基类中的成员是private,继承下来在子类中都是不可见的,而除了私有以外,我们发现都是可见的。基类的其他成员在子类中的访问方式==min(成员在基类的访问限定符,继承方式)public>protected>private
不可见是什么呢?就是在父类中可以正常访问成员变量,而继承下来的子类在语法上限制访问,不管是在类外面还是在类里面都是不能使用的。

struct默认继承方式是public,而class 默认继承方式是private;

class person
{
protected:
	string _name = "zhangsan";
};

class student:person//默认继承方式是private
{
public:
	void print()
	{
		cout << _name << endl;
	}
};
struct person
{
	string _name = "zhangsan";
};

class student :person//默认继承方式是public
{
public:
	void print()
	{
		cout << _name << endl;
	}
};

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

派生类对象可以赋值给基类的对象、基类的指针以及基类的引用,因为在这个过程中,会发生基类和派生类对象之间的赋值转换.
对于下面基类和派生类

class Person
{
protected:
	string _name; // 姓名
	string _sex;//性别
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};

赋值转换

void test()
{
	Student s;
	Person p = s;//派生类的对象的值赋给基类
	Person& rp = s;//派生类的对象的值赋给基类的引用
	Person* ptr = &s;//派生类的指针赋给基类的指针
}
int main()
{
	test();
	return 0;
}

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割。寓意把派生类中父类那部分切来赋值过去。
派生类对象把值赋给基类
在这里插入图片描述
派生类对象把只给基类的引用,使得rp变成派生类中父类的成员变量,改变rp也就会改变派生类中父类的成员变量。
在这里插入图片描述
派生类的指针赋给基类的指针,该父类的指针指向派生类中父类的成员变量。
在这里插入图片描述

继承中的作用域

每个子类和父类都有其特地的作用域,子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。

class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111; // 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 学号:" << _num << endl;
	}
protected:
	int _num = 999; // 学号
};

在这边我们会发现它会优先打印子类的_num,我们想要打印父类的_num的时候就可以指定访问。

cout << " 身份证号:" << Person::_num << endl;
cout << " 学号:" << _num << endl;

如果成员函数重名呢?

class A {
public:
	void fun()
	{
		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(10);
};

我们发现这个是函数重载吗?还是构成隐藏呢?根据我们之前学的定义是函数名相同,类型不同的话就构成函数重载。

void Test()
{
 B b;
 b.fun(10);//不能直接写成b.func()他会优先调用自己的fun函数。
 b.A::fun();
};

特别注意: 代码当中,父类中的fun和子类中的fun不是构成函数重载,因为函数重载要求两个函数在同一作用域,而此时这两个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:
	Student(const char* name, int num)
		: Person(name)//显式调用父类的构造初始化父类的那一部分成员。
		, _num(num)//自己成员的初始化
	{
		cout << "Student()" << endl;
	}

	Student(const Student& s)
		: Person(s)//调用父类的拷贝构造来初始化父类的那一部分成员变量,这边就运用了我们切割部分的知识点
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);//调用父类的运算符重载来初始化父类的成员变量
			_num = s._num;
		}
		return *this;
	}

	~Student()
	{
		cout << "~Student()" << endl;//这边就不要调用父类的析构了,我们编译器会自动调用父类的析构,我们保证先析构子类的,然后在析构父类的
	}
protected:
	int _num; //学号
};

派生类与普通类的默认成员函数的不同之处概括为以下几点:

派生类的构造函数被调用时,会自动调用基类的构造函数初始化基类的那一部分成员,如果基类当中没有默认的构造函数,则必须在派生类构造函数的初始化列表当中显示调用基类的构造函数。
派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类成员的拷贝构造。
派生类的赋值运算符重载函数必须调用基类的赋值运算符重载函数完成基类成员的赋值。
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。
派生类对象初始化时,会先调用基类的构造函数再调用派生类的构造函数。
派生类对象在析构时,会先调用派生类的析构函数再调用基类的析构函数。

在编写派生类的默认成员函数时,需要注意以下几点

派生类和基类的赋值运算符重载函数因为函数名相同构成隐藏,因此在派生类当中调用基类的赋值运算符重载函数时,需要使用作用域限定符进行指定调用。
由于多态的某些原因,任何类的析构函数名都会被统一处理为destructor();。因此,派生类和基类的析构函数也会因为函数名相同构成隐藏,若是我们需要在某处调用基类的析构函数,那么就要使用作用域限定符进行指定调用。
在派生类的拷贝构造函数和operator=当中调用基类的拷贝构造函数和operator=的传参方式是一个切片行为,都是将派生类对象直接赋值给基类的引用。

注意:

我们在构造函数,拷贝构造,赋值运算符重载中都可以显示调用,但是在析构函数中不能显示调用,基类的析构函数是当派生类的析构函数被调用后由编译器自动调用的,我们若是自行调用基类的构造函数就会导致基类被析构多次的问题。
我们知道,创建派生类对象时是先创建的基类成员再创建的派生类成员,编译器为了保证析构时先析构派生类成员再析构基类成员的顺序析构,所以编译器会在派生类的析构函数被调用后自动调用基类的析构函数。

继承与友元

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

我们不可以继承父类的里面的,友元,要想在派生类也想访问,只能在派生类中声明。友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

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

继承与静态成员
若基类当中定义了一个static静态成员变量,则在整个继承体系里面只有一个该静态成员。无论派生出多少个子类,都只有一个static成员实例。

例如,在基类Person当中定义了静态成员变量_count,尽管Person又继承了派生类Student和Graduate,但在整个继承体系里面只有一个该静态成员。
我们若是在基类Person的构造函数和拷贝构造函数当中设置_count进行自增,那么我们就可以随时通过_count来获取该时刻已经实例化的Person、Student以及Graduate对象的总个数

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::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}

我们派生类继承的是父类的静态成员的使用权,从头到尾只有一个静态成员。

cout << &Person::_count << endl; //00F1F320

cout<< &Student::_count << endl; //00F1F320

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

单继承:一个子类只有一个直接父类时称这个继承关系为单继承;

在这里插入图片描述

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

在这里插入图片描述

菱形继承:菱形继承是多继承的一种特殊情况。

在这里插入图片描述
从菱形继承的模型构造就可以看出,菱形继承的继承方式存在数据冗余和二义性的问题。

数据冗余
• 定义:同一份数据在系统中被重复存储多次。如上图有俩份A类的数据
二义性
• 定义:同一操作可能对应多个冲突的语义,导致无法明确执行。在B的变量中有A,在C中也有A.

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

int main()
{
	Assistant at;
	at._name = "张三";//就会存在二义性的问题
	return 0;
}

解决二义性的问题的方法:指定访问可以解决

int main()
{
	Assistant at;
	at.Student::_name = "张三";//就会存在二义性的问题
	at.Teacher::_name = "王丽";
	return 0;
}

但是如何解决数据冗余的问题呢?因为在Assistant的对象在Person成员始终会存在两份

在这里插入图片描述
菱形虚拟继承
为了解决菱形继承的二义性和数据冗余问题,出现了虚拟继承。如前面说到的菱形继承关系,在Student和Teacher继承Person是使用虚拟继承,即可解决问题。

在这里插入图片描述
在使用菱形虚拟继承的之前,我们可以先看看之前他们的的情况做个对比

class A {
public:
	int _a;
};
// class B : public A
class B :  public A {
public:
	int _b;
};
// class C : public A
class C :  public A {
public:
	int _c;
};
class D : public B, public C {
public:
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

在这里插入图片描述
使用菱形继承之后内存窗口的变化

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;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

在这里插入图片描述
通过这俩个对比我们可以发现一个问题就是对象D中_a的好像带最后一个位置去了,然后本来虚拟继承之前B中_a,C中_a的位置怎么好像变成指针了。
在这里插入图片描述
这俩个指针是啥呢?我们可以多开几个内存窗口查看一下。
在这里插入图片描述
在这里插入图片描述
我们发现指向指向的第一个位置是0,在第一个_b中我们指针指向的下一个位置是14,在16进制中14就表示20的意思,怎么得来的呢?
在这里插入图片描述
我们用_a的地址减去上面指针的地址所得到的偏移量就是20,转化为16进制就是14.同理另一个计算所得到的偏移量是12,16进制就是c.
在这里插入图片描述
如下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下
面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A。
在这里插入图片描述
我们若是将D类对象赋值给B类对象,在这个切片过程中,就需要通过虚基表中的第二个数据找到公共虚基类A的成员,得到切片后该B类对象在内存中仍然保持这种分布情况。

B b=d;
b ._a=1;
b._b=2;

在这里插入图片描述
总结:

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

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

相关文章

PyTorch 学习笔记

环境&#xff1a;python3.8 PyTorch2.4.1cpu PyCharm 参考链接&#xff1a; 快速入门 — PyTorch 教程 2.6.0cu124 文档 PyTorch 文档 — PyTorch 2.4 文档 快速入门 导入库 import torch from torch import nn from torch.utils.data import DataLoader from torchvision …

Spring AI 结构化输出详解

一、Spring AI 结构化输出的定义与核心概念 Spring AI 提供了一种强大的功能&#xff0c;允许开发者将大型语言模型&#xff08;LLM&#xff09;的输出从字符串转换为结构化格式&#xff0c;如 JSON、XML 或 Java 对象。这种结构化输出能力对于依赖可靠解析输出值的下游应用程…

从零开始的C++编程 2(类和对象下)

目录 1.构造函数初始化列表 2.类型转换 3.static成员 4.友元 5.内部类 6.匿名对象 1.构造函数初始化列表 ①之前我们实现构造函数时&#xff0c;初始化成员变量主要使⽤函数体内赋值&#xff0c;构造函数初始化还有⼀种⽅式&#xff0c;就是初始化列表&#xff0c;初始化…

AI结合VBA提升EXCEL办公效率尝试

文章目录 前言一、开始VBA编程二、主要代码三、添加到所有EXCEL四、运行效果五、AI扩展 前言 EXCEL右击菜单添加一个选项&#xff0c;点击执行自己逻辑的功能。 然后让DeepSeek帮我把我的想法生成VBA代码 一、开始VBA编程 我的excel主菜单没有’开发工具‘ 选项&#xff0c;…

Python快速入门指南:从零开始掌握Python编程

文章目录 前言一、Python环境搭建&#x1f94f;1.1 安装Python1.2 验证安装1.3 选择开发工具 二、Python基础语法&#x1f4d6;2.1 第一个Python程序2.2 变量与数据类型2.3 基本运算 三、Python流程控制&#x1f308;3.1 条件语句3.2 循环结构 四、Python数据结构&#x1f38b;…

Java——数据类型与变量

文章目录 字面常量Java数据类型变量定义变量的方式整形变量长整型变量短整型变量字节型变量浮点型变量双精度浮点型单精度浮点型 字符型变量布尔型变量 类型转换自动类型转换&#xff08;隐式&#xff09;强制类型转换&#xff08;显式&#xff09; 类型提升byte与byte的运算 字…

9. C++STL详解vector的使用以及模拟实现

文章目录 一、vector的使用介绍1.1 vector的定义1.2 vector iterator 的使用1.3 vector 增删查改二、vector 迭代器失效问题会引起其底层空间改变的操作&#xff0c;都有可能是迭代器失效&#xff0c;比如&#xff1a;resize、reserve、insert、assign、push_back等。指定位置元…

C/C++调用Python程序代码实现混合编程笔记教程

0、引言 Python‌在基础开发、数据科学、人工智能、Web框架开发等领域具有广泛的支持工具和开发教程&#xff0c;极大的缩短了产品原型开发周期、降低了开发难度。 有许多的功能&#xff0c;通过C/C实现&#xff0c;非常的复杂并且不方便&#xff0c;但是Python可能就是几行代码…

LeetCode hot 100—子集

题目 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[1],[2…

Linux网络编程——数据链路层详解,以太网、MAC地址、MTU、ARP、DNS、NAT、代理服务器......

目录 一、前言 二、以太网 二、以太网帧格式 三、 MAC地址 四、MTU 1、数据链路层的数据分片 2、MTU对UDP协议的影响 3、MTU对TCP协议的影响 五、ARP协议 1、什么是ARP 2、ARP的作用 3、ARP协议的工作流程 4、ARP缓存表 5、ARP请求报文 6、中间人 六、DNS&…

基于springboot+vue的秦皇岛旅游景点管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系统展示 用户登录 旅游路…

Linux网络编程——TCP通信的四次挥手

一、前言 上篇文章讲到了TCP通信建立连接的“三次握手”的一些细节&#xff0c;本文再对TCP通信断开连接的“四次挥手”的过程做一些分析了解。 二、TCP断开连接的“四次挥手” 我们知道TCP在建立连接的时需要“三次握手”&#xff0c;三次握手完后就可以进行通信了。而在通…

计算机视觉算法实现——SAM实例分割:原理、实现与应用全景

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​ ​​​​​​​​​ ​​ 1. 实例分割领域概述 实例分割(Instance Segmentation)是计算机视觉领域最具挑战性的任务之一&#xff0c…

基于SpringBoot的宠物健康咨询系统(源码+数据库+万字文档)

502基于SpringBoot的宠物健康咨询系统&#xff0c;系统包含三种角色&#xff1a;管理员、用户&#xff0c;顾问主要功能如下。 【用户功能】 1. 首页&#xff1a;查看系统主要信息和最新动态。 2. 公告&#xff1a;浏览系统发布的公告信息。 3. 顾问&#xff1a;浏览可提供咨询…

vue2 el-element中el-select选中值,数据已经改变但选择框中不显示值,需要其他输入框输入值才显示这个选择框才会显示刚才选中的值

项目场景&#xff1a; <el-table-column label"税率" prop"TaxRate" width"180" align"center" show-overflow-tooltip><template slot-scope"{row, $index}"><el-form-item :prop"InquiryItemList. …

CCF CSP 第35次(2024.09)(2_字符串变换_C++)(哈希表+getline)

CCF CSP 第35次&#xff08;2024.09&#xff09;&#xff08;2_字符串变换_C&#xff09; 解题思路&#xff1a;思路一&#xff08;哈希表getline&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;哈希表getline&#xff09;&#xff09;&#xff1a; …

Docker--利用dockerfile搭建mysql主从集群和redis集群

Docker镜像制作的命令 链接 Docker 镜像制作的注意事项 链接 搭建mysql主从集群 mysql主从同步的原理 MySQL主从同步&#xff08;Replication&#xff09;是一种实现数据冗余和高可用性的技术&#xff0c;通过将主数据库&#xff08;Master&#xff09;的变更操作同步到一个…

蓝桥杯嵌入式考前模块总结

一.RTC 使用RTC直接再cubeMX中配置启动时钟和日历 如第六届省赛 想要让RTC的秒每隔一秒递增1需要在时钟树界面观察RTC的主频 由于RTC时钟主频为32KHZ将异步预分频计数器的值设为31&#xff0c;将同步预分频计数器的值设为999这样就可以将RTC的时钟信号分频为1HZ达到1秒自增的…

关于举办“2025年第五届全国大学生技术创新创业大赛“的通知

赛事含金量 大赛获奖即可有机会为你的大学里的“创新创业”加分&#xff01;这是每个大学要求必须修满的学分&#xff01; 中国“互联网&#xff0b;”大学生创新创业大赛磨刀赛&#xff01;“挑战杯”中国大学生创业计划大赛必参赛&#xff01; 国赛获奖&#xff0c;“互联…

Ingress蓝绿发布

Ingress蓝绿发布 Ingress常用注解说明yaml资源清单绿色版本yml资源清单蓝色版本yaml资源清单 主Ingress金丝雀Ingress基于客户端请求头的流量切分结果验证 基于客户端来源IP的流量切分结果验证 基于服务权重的流量切分结果验证 基于IP来源区域来切分IP---方案未验证基于User-Ag…