【C++深度探索】:继承(定义赋值兼容转换作用域派生类的默认成员函数)

news2024/11/26 12:28:07

✨                                                       愿随夫子天坛上,闲与仙人扫落花       🌏 

📃个人主页:island1314

🔥个人专栏:C++学习

🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞


一、继承的概念及定义

1.1继承的概念

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

        继承的父类的成员(成员变量+成员函数)不过是父类的成员变量拷贝给子类,并不指向同一个,子类中可以使用父类的成员变量,在子类里的改变不影响父类,但是成员函数是同一个

1.2 继承定义

1.2.1定义格式

由上面我们可以看到Person是父类,也称作基类。Student是子类,也称作派生类。

1.2.2继承关系和访问限定符

💞访问限定符:

 C++类的访问限定符用于控制类的成员(包括成员变量和成员函数)在类的外部的可访问性。   C++中有以下三种访问限定符:

  • public: 公共访问限定符,任何地方都可以访问公共成员。可以在类的外部使用对象名和成员名直接访问公共成员。

  • private: 私有访问限定符,只有类内部的其他成员函数可以访问私有成员。类的外部无法直接访问私有成员,但可以通过公共成员函数间接访问私有成员。

  • protected: 保护访问限定符,只有类内部的其他成员函数和派生类的成员函数可以访问保护成员。类的外部无法直接访问保护成员,但可以通过公共成员函数或派生类的成员函数间接访问保护成员。

需要注意的是,访问限定符只在类的内部起作用,在类的外部没有直接的影响。同时,访问限定符可以用于类的成员变量和成员函数的声明中,默认情况下,成员变量和成员函数的访问限定符是private。

  

💞继承方式:

C++类的继承方式有以下几种:

  • 公有继承(public inheritance):使用关键字"public"表示的继承方式。在公有继承中,基类的公有成员和保护成员都可以在派生类中访问,私有成员不能在派生类中直接访问。
  • 保护继承(protected inheritance):使用关键字"protected"表示的继承方式。在保护继承中,基类的公有成员和保护成员在派生类中都变为保护成员私有成员不能在派生类中直接访问。
  • 私有继承(private inheritance):使用关键字"private"表示的继承方式。在私有继承中,基类的公有成员和保护成员在派生类中都变为私有成员,私有成员不能在派生类中直接访问。
class Base {
public:
    // 公有成员
protected:
    // 保护成员
private:
    // 私有成员
};
class Derived : public Base {
    // 公有继承
}

class Derived : protected Base {
    // 保护继承
};

class Derived : private Base {
    // 私有继承
};
1.2.3 继承基类成员访问方式的变
类成员/继承方式    public继承    protected继承  private继承
基类的public成员 派生类的public成员   派生类的protected成员    派生类的private成员
基类的protected成员 派生类的protected成员    派生类的protected成员    派生类的private成员
基类的private成员   在派生类中不可见     在派生类中不可见在派生类中不可见

总结如下:

①基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它

②基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。

③基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private

这些继承方式可以根据具体的需求选择合适的方式

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

⑤在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "Peter"; //名字
	int _age = 18;  //年龄
private:
	//父类定义私有的本质:不想被子类继承
	//但是在子类中有这个成员,不能直接使用,可以间接使用
	int _tel = 110;
};

//基类的private成员,子类继承后在类外或在类中都不能访问
class Student : public Person
{
public:
	void Func()
	{
		//子类用不了(不可见)
		//cout << _tel << endl;

		//子类可以用
		cout << _name << endl;
		cout << _age << endl;
	}
protected:
	int _stuid; // 学号
};

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

int main()
{
	Student s;
	s.Print();

	Person p;
	p.Print();

	return 0;
}

父类私有,子类对象不能直接访问使用,但是可以调用父类成员函数间接使用

二,基类和派生类对象赋值兼容转换

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

💞如下图所示:

(2) 基类对象不能赋值给派生类对象

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;
    Person p;
	// 1.子类对象可以赋值给父类对象/指针/引用
	
    //基类和派生类的赋值兼容转换
    p = s;
	
    Person* Ptr = &s;//指向子类当中父类的那一部分
    
    //引用的是子类当中父类的那一部分,
	//引用后这两者指向同一个东西,子类改变父类也变
	Person& ref = s;

	//2.基类对象不能赋值给派生类对象
	s = p;//error
					
	return 0;
}

✨注意:这里的赋值兼容转换与C语言里的类型截断,提升毫无关系,意义也完全不同

C语言中的类型截断,提升本质是类型转换,转换过程中会产生临时变量。而赋值兼容转换是一种特殊的语法规则,中间没有产生临时变量

三,继承中的作用域

(1) 在继承体系中基类和派生类都有独立的作用域

(2) 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问

  • 如果要访问被隐藏的父类的同名成员,可以在子类成员函数中,使用 父类::父类成员来显示访问

(3) 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏,不管参数和返回值。子类和父类中可以有同名成员变量,因为它们属不同的类域,同一类里不可以

(4) 注意在实际中在继承体系里面最好不要定义同名的成员。

如下面的代码中,此时s里就有两个_num,默认访问的是自己的,如果想访问父类的,指定作用域即可

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout << "num:" << _num << endl;
	}
protected:
	string _name = "Peter"; //名字
	int _age = 18;  //年龄
	int _num = 999;
};

class Student : public Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout << "num:" << _num << endl;
		
		//指定
		cout << "num:" << Person::_num << endl;

	}
protected:
	int _stuid; // 学号
	int _num = 111;

};

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

//继承中的作用域

//子类和父类中可以有同名成员变量,因为它们属不同的类域
//同一类里不可以
//此时s里就有两个_num,默认访问的是自己的(隐藏/重定义),
// 如果想访问父类的,指定作用域即可

//如果是成员函数的隐藏,函数名相同就是隐藏,不管参数和返回值

int main()
{
	Student s;

	//子类对象调用Print,先去子类里找,没有,再去父类中找
	s.Print();

	return 0;
}

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

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

这里重点介绍构造函数,拷贝构造,赋值拷贝和析构函数

在讨论这个问题时,需要划分成下面三块:

父类成员(整体)
子类自己的内置成员
子类自己的自定义成员

先给出基类:

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; // 姓名
};

🔥1、构造函数

子类默认生成的构造
(1) 父类成员(整体) – 调父类的默认构造
(2) 子类自己的内置成员 – 一般不做处理
(3) 子类自己的自定义成员 – 默认构造

父类的成员变量不能在子类中构造,规定只能调用父类的构造。

注意:
(1) 构造初始化时,要先父后子,因为子类构造初始化可能会用父类成员。若没有初始化父类,父类成员就是随机值,此时再用它构造子类就会出问题

(2) 成员变量的初始化顺序和初始化列表的顺序无关,而是与变量的声明顺序有关。在C++的初始化列表中,是默认先走父类构造初始化的

Student(const char* name = "", int x = 0, const char* address = "")
	//:_name(name) //规定了不能直接碰父类成员
	//: Person(name)
	//父类构造显示调用,可以保证先父后子
	:_x(x)
	,_address(address)
	,_name(Person::_name+'x')
	,Person(name)
{}

🔥2、拷贝构造

子类默认生成拷贝构造
(1) 父类成员(整体) – 调父类的拷贝构造
(2) 子类自己的内置成员 – 值拷贝
(3) 子类自己的自定义成员 – 调用自己的拷贝构造

注意:
(1) 一般不需要自己写,当子类成员涉及深拷贝时,就必须自己实现

(2) 调用父类的拷贝构造时,直接传子类对象过去,会自然的切割,赋值兼容转换

Student(const Student& st)
	:Person(st)   //调用父类的拷贝构造
	,_x(st._x)
	,_address(st._address)
{}

🔥3、赋值拷贝

子类默认生成赋值拷贝
(1) 父类成员(整体) – 调父类的赋值拷贝
(2) 子类自己的内置成员 – 值拷贝
(3) 子类自己的自定义成员 – 调用自己的赋值拷贝

注意:
(1) 一般不需要自己写,当子类成员涉及深拷贝时,就必须自己实现

(2)由于子类与父类的operator=()是隐藏关系,这里要指定类域,防止子类一直调用自己的,造成死循环

Student& operator=(const Student &st)
{
	if (this != &st)
	{
		//operator = (st); //错误
		Person::operator= (st);//指定
		_x = st._x;
		_address = st._address;
	}
	return *this;
}

🔥4、析构函数

子类默认生成的析构
(1) 父类成员(整体) – 调父类的析构
(2) 子类自己的内置成员 – 不做处理
(3) 子类自己的自定义成员 – 调用自己的析构

注意:
父类析构不能显示调用,因为显示调用不能保证先子后父。析构时要先子后父,因为子类析构是可能会用到父类成员的。若先父后子,则父类成员会先析构一次,子类再析构就出问题了。

因此父类析构不是自己显式调用的,会在子类析构结束后自动调用。

//显示写析构
// 由于多态,析构函数的名字会被统一处理为destructor()
~Student()// 析构时先子后父
{
	//父类析构不能显示调用,因为显示调用不能保证先子后父
	// 析构函数会构成隐藏,因此要统一处理
	//Person::~Person();
	cout << "~Student()" << endl;
}

最后代码综合分析:

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 x = 0, const char* address = "")
		:_x(x)
		,_address(address)
		,_name(Person::_name+'x')
		,Person(name)
	{}

	Student(const Student& st)
		:Person(st)   
		,_x(st._x)
		,_address(st._address)
	{}

	Student& operator=(const Student &st)
	{
		if (this != &st)
		{
			Person::operator= (st);
			_x = st._x;
			_address = st._address;
		}
		return *this;
	}

	~Student()// 析构时先子后父
	{
		//Person::~Person();
		cout << "~Student()" << endl;
	}

protected :
	int _x = 1;
	string _address = "湖南";
	string _name;
};

void Test2()
{
	Student s1; 
	Student s2("张三", 18, "长沙");

	Student2 s3 = s2; 

	s1 = s3;

}

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

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

相关文章

pin是什么?管脚

1.平面分割 1)启动Allegro PCB design &#xff0c;打开.brd。深色部分属于一个net&#xff0c;要做一下修改&#xff0c;将上面的pin包含进shape中&#xff0c;i进行a&#xff0c;b两步操作&#xff0c;删除以前存在的Anti Etch下的line&#xff0c;再将其进行补齐 使它保住上…

MSPM0G3507——OPENMV给M0传数据(用数据包)互相通信(以循迹为例)

OPENMV端代码 # main.py -- put your code here! import pyb, sensor, image, math, time from pyb import UART import ustruct from image import SEARCH_DS, SEARCH_EX import time import sensor, displayuart UART(3, 115200, bits8, parityNone, stop1, timeout_char10…

Pogo-DroneCANPWM模块:可实现DroneCAN转PWM,DroneCAN转dshot,DroneCAN转bdshot

关键词&#xff1a;Ardupilot&#xff0c;Pixhawk&#xff0c;PWM&#xff0c;dshot&#xff0c;bdshot&#xff0c;DroneCANPWM&#xff0c;电调ESC&#xff0c;DroneCAN&#xff0c;UAVCAN&#xff0c;飞控&#xff0c;无人机&#xff0c;UAV Keywords&#xff1a;Ardupilot…

Xilinx FPGA:vivado串口输入输出控制fifo中的数据

一、实验要求 实现同步FIFO回环测试&#xff0c;通过串口产生数据&#xff0c;写入到FIFO内部&#xff0c;当检测到按键信号到来&#xff0c;将FIFO里面的数据依次读出。 二、信号流向图 三、状态转换图 四、程序设计 &#xff08;1&#xff09;按键消抖模块 timescale 1ns…

Python编程学习笔记(1)--- 变量和简单数据类型

1、变量 在学习编程语言之前&#xff0c;所接触的第一个程序&#xff0c;绝大多数都是&#xff1a; print("Hello world!") 接下来尝试使用一个变量。在代码中的开头添加一行代码&#xff0c;并对第二行代码进行修改&#xff0c;如下&#xff1a; message "…

Github 2024-07-07php开源项目日报 Top9

根据Github Trendings的统计,今日(2024-07-07统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9Blade项目2JavaScript项目1Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数…

什么时候考虑将mysql数据迁移到ES?

文章目录 对ES的一些疑问问题1:ES相比mysql本身有哪些优势&#xff1f;问题2:哪些场景适合用ES而不是mysql&#xff1f;问题3:mysql逐行扫描&#xff0c;根据过滤条件检查记录中对应字段是否满足要求属于正排索引&#xff0c;根据二叉树索引检索记录的方式属于正排索引还是倒排…

LeetCode 189.轮转数组 三段逆置 C写法

LeetCode 189.轮转数组 C写法 三段逆置 思路: 三段逆置方法:先逆置前n-k个 再逆置后k个 最后整体逆置 由示例1得&#xff0c;需要先逆置1,2,3,4 再逆置5,6,7&#xff0c;最后前n-k个与后k个逆置 代码 void reverse(int*num, int left, int right) //逆置函数 { while(left …

XLSX + LuckySheet + LuckyExcel + Web Worker实现前端的excel预览

文章目录 功能简介简单代码实现web worker 版本效果参考 功能简介 通过LuckyExcel的transformExcelToLucky方法&#xff0c; 我们可以把一个文件直接转成LuckySheet需要的json字符串&#xff0c; 之后我们就可以用LuckySheet预览excelLuckyExcel只能解析xlsx格式的excel文件&a…

mac|idea导入通义灵码插件

官方教程&#xff1a;通义灵码下载安装指南_智能编码助手_AI编程_云效(Apsara Devops)-阿里云帮助中心 下载插件&#xff1a; ⇩ TONGYI Lingma - JetBrains 结果如下&#xff1a; 选择apply、ok&#xff0c;会出现弹窗&#xff0c;点击登录 可以实现&#xff1a;生成单元测…

SQL Server特性

一、创建表 在sql server中使用create table来创建新表。 create table Customers( id int primary key identity(1,1), name varchar(5) ) 该表名为Customers其中包含了2个字段&#xff0c;分别为id&#xff08;主键&#xff09;以及name。 1、数据类型 整数类型&#xff…

【TB作品】51单片机 Proteus仿真 00002仿真-智能台灯色调倒计时光强

实验报告&#xff1a;基于51单片机的智能台灯控制系统 背景 本实验旨在设计一个基于51单片机的智能台灯控制系统&#xff0c;该系统可以通过按键进行手动控制&#xff0c;并能根据环境光强度自动调节台灯亮度。此外&#xff0c;系统还具备倒计时关灯功能。 器件连接 51单片…

latex英文转中文word,及一些latex相关工具分享

前言&#xff1a;想要转换latex生成的英文pdf文件为中文word文件 一、主要步骤 1、文字翻译&#xff1a;直接使用谷歌翻译等辅助将英文翻译成中文即可&#xff1b; 2、图片&#xff1a; 使用latex时一般保存的.png&#xff0c;.bmp格式图片可以直接插入word, 但是.eps或者 .p…

期末成绩发布方式

期末考试结束后&#xff0c;成绩单的发放总是让老师们头疼不已。想象一下&#xff0c;每个学生的成绩都需要老师一个个私信给家长&#xff0c;不仅耗时耗力&#xff0c;而且极易出错。 在传统的成绩单发放方式中&#xff0c;老师往往需要通过电子邮件、短信或者微信等方式&…

使用Keil将STM32部分程序放在RAM中运行

手动分配RAM区域,新建.sct文件,定义RAM_CODE区域,并指定其正确的起始地址和大小。 ; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************…

高考志愿填报千万要注意这四点

在高考志愿填报过程中&#xff0c;确实有很多需要留心的点。我为你总结了四个关键点&#xff0c;希望能帮助你顺利完成志愿填报&#xff1a; 1、学校提供的支持 学校作为学生志愿填报咨询服务的主阵地&#xff0c;应提供体系化和制度化的支持。包括及时关注并传达政策动向和相…

ubuntu下运行程序时提示缺库问题的有效解决方法

目录 一、问题现象二、解决方式三、总结 一、问题现象 当我们平时在ubuntu上运行一个程序时时长会遇到如下情况&#xff0c;含义为本机缺少执行程序需要的库 这时候我们可能会根据缺少的库使用apt install 库名的模糊名字 进行安装&#xff0c;然后再去运行&#xff0c;此时可…

vue3+antd 实现文件夹目录右键菜单功能

原本的目录结构&#xff1a; 右键菜单&#xff1a; 点击菜单以后会触发回调&#xff1a; 完整的前端代码&#xff1a; <template><a-directory-treev-model:expandedKeys"expandedKeys"v-model:selectedKeys"selectedKeys"multipleshow-li…

C语言下结构体、共用体、枚举类型的讲解

主要内容 结构体结构体数组结构体指针包含结构体的结构链表链表相关操作共用体枚举类型 结构体 结构体的类型的概念 结构体实现步骤 结构体变量的声明 struct struct 结构体名{ 数据类型 成员名1; 数据类型 成员名2; ..…

【Unity】unity学习扫盲知识点

1、建议检查下SystemInfo的引用。这个是什么 Unity的SystemInfo类提供了一种获取关于当前硬件和操作系统的信息的方法。这包括设备类型&#xff0c;操作系统&#xff0c;处理器&#xff0c;内存&#xff0c;显卡&#xff0c;支持的Unity特性等。使用SystemInfo类非常简单。它的…