【C++继承】

news2024/11/17 9:24:05

目录

  • 一、继承的概念及定义
    • 1.1继承的概念
    • 1.2继承的定义
      • 1.2.1定义格式
      • 1.2.2继承方式与访问限定符的组合
  • 二、基类和派生类对象赋值转换
  • 三、继承中的作用域
  • 四、派生类的默认成员函数
  • 五、继承与友元
  • 六、继承与静态成员
  • 七、复杂的菱形继承及菱形虚拟继承
  • 八、虚拟继承的原理

一、继承的概念及定义

1.1继承的概念

继承是面向对象的三大特性之一。从字面上再结合程序员的特性就可以大概了解继承的主要思想,
通俗的讲就是继承过来的拥有被继承的属性。它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生的新类,称派生类(或子类),被继承的类称基类(或父类)。如:

#include<iostream>
using namespace std;

class person
{
public:
	char* _name;//名字
	char* _Id;  //身份证
	size_t _age;//年龄
	int _sex;   //1表示男  0表示女
};
class student :public person
{
public:
	int _level;//年级
	string _major;//专业
};
class teacher :public person
{
public:
	int _job_title;//职称
};
int main()
{
	return 0;
}

简单来说呢就是person是teacher与student相同的部分所抽出来的。这样的好处就是方便了代码的编写。程序员的最大特点就是会考虑代码的复用,特别是资深的程序员极其讨厌写重复的代码。
在这里插入图片描述

1.2继承的定义

1.2.1定义格式

在这里插入图片描述
继承方式与访问限定符一样一共有三种。

1.2.2继承方式与访问限定符的组合

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

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

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

class person
{
public:
	char* _name;//名字
	char* _Id;  //身份证
	size_t _age;//年龄
	int _sex;   //1表示男  0表示女
};

class student :public person
{
public:
	int _level;//年级
	string _major;//专业
};

int main()
{
	person p;
	student s;

	p = s;
	return 0;
}

在这里插入图片描述
注意这里并没有发生隐式的类型转化。如何来证明呢?如下:
在这里插入图片描述

三、继承中的作用域

1.子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员 显示访问)
2.需要注意的是如果成员函数的隐藏,只需要函数名相同就构成隐藏。
3.注意在实际中在继承体系里面最好不要定义同名的成员。
如:
在这里插入图片描述
在这里插入图片描述
两个func不会构成重载,是隐藏/重定义关系。要记住重载是发生在同一作用域下的。

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

1.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

class person
{
public:
	person(const char* name="左方婷")
		:_name(name)
	{
	}
protected:
	string _name;
};

class student : public person
{
public:
	student(const char* name = "zuofangting", int num = 20)
		:_num(20),
		person()
	{}
protected:
	int _num;
};

int main()
{
	return 0;
}
  1. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
student(const student& s)
	:_num(s._num),
	person(s)
{
	cout << "student的拷贝构造函数调用" << endl;
}

这里的person(s)就是上面讲的切片现象。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。

student& operator=(const student& s)
{
	if (this != &s)
	{
		_num = s._num;
		person::operator=(s);
	}
	return *this;
}

这里必须要指定是person的作用域下的=重载,不然会无线调用自己的operator=。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
保证派生类对象先清理派生类成员再清理基类成员的顺序。不需要我们自己调用派生类的析构。
在这里插入图片描述
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。
7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个后面会讲
解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加
virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。如:
在这里插入图片描述

五、继承与友元

一句话概括就是你与父亲是好朋友并不能保证你与我也是好朋友。

class student;
class person
{

public:
	friend void Print(const person& p, const student& s);

	person(const char* name="左方婷")
		:_name(name)
	{
		cout << "person的构造调用" << endl;
	}

	person(const person& p)
		:_name(p._name)
	{
		cout << "person的拷贝构造调用" << endl;
	}

	person& operator=(const person& p)
	{
		_name = p._name;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}
protected:
	string _name;
};



class student : public person
{

public:
	friend void Print(const person& p, const student& s);

	student(const char* name = "zuofangting", int num = 20)
		:_num(20),
		person(name)
	{
		cout << "student的构造调用" << endl;
	}

	student(const student& s)
		:_num(s._num),
		person(s)
	{
		cout << "student的拷贝构造函数调用" << endl;
	}

	student& operator=(const student& s)
	{
		if (this != &s)
		{
			_num = s._num;
			person::operator=(s);
		}
		return *this;
	}

	~student()
	{
		//person::~person();
		cout << "student的析构函数调用" << endl;
	}
protected:
	int _num;
};

void Print(const person& p, const student& s)
{
	cout << p._name << endl;
	cout << s._num << endl;

}

int main()
{
	student s1("wuzhilong",12);
	//student s2(s1);

	return 0;
}

六、继承与静态成员

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

class person
{

public:


	person(const char* name="左方婷")
		:_name(name)
	{
		++_count;
		cout << "person的构造调用" << endl;
	}


	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

	static int _count;
protected:
	string _name;
};

int person::_count = 0;

class student : public person
{

public:


	student(const char* name = "zuofangting", int num = 20)
		:_num(20),
		person(name)
	{
		cout << "student的构造调用" << endl;
	}

	~student()
	{
		//person::~person();
		cout << "student的析构函数调用" << endl;
	}
protected:
	int _num;
};



int main()
{
	student s1("wuzhilong",12);
	//student s2(s1);
	cout << person::_count << endl;
	return 0;
}

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

在这里插入图片描述
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
在Assistant的对象中Person成员会有两份。

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; // 主修课程
};
void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

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

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

	Assistant a;
	a._name = "peter";

}
int main()
{
	return 0;
}

八、虚拟继承的原理

在这里插入图片描述
下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下
面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A。

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

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

相关文章

(AcWing)满足条件的01序列

给定 n 个 0 和 n 个 1&#xff0c;它们将按照某种顺序排成长度为 2n 的序列&#xff0c;求它们能排列成的所有序列中&#xff0c;能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列有多少个。 输出的答案对 10^97 取模。 输入格式 共一行&#xff0c;包含整数 n。 …

Kotlin基础(十):函数进阶

前言 本文主要讲解kotlin函数&#xff0c;之前系列文章中提到过函数&#xff0c;本文是kotlin函数的进阶内容。 Kotlin文章列表 Kotlin文章列表: 点击此处跳转查看 目录 1.1 函数基本用法 Kotlin 是一种现代的静态类型编程语言&#xff0c;它在函数的定义和使用上有一些特点…

软考A计划-系统集成项目管理工程师-项目干系人管理-上

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

简单易用的批量重命名工具,C++语言编写

它具备出色的文件重命名功能,能够让用户轻松对多个文件进行批量重命名操作。不论是添加前缀、后缀,还是替换文件名称中的特定字符,都能轻松完成。此外,该软件体积小巧、操作简单便捷,使用起来的效果出奇好。 MiniRenamer特色功能: 正则命名:支持正则命名规则,并可自定义…

【机器学习】Classification using Logistic Regression

Classification using Logistic Regression 1. 分类问题2. 线性回归方法3. 逻辑函数&#xff08;sigmod&#xff09;4.逻辑回归5. 决策边界5.1 数据集5.2 数据绘图5.3 逻辑回归与决策边界的刷新5.4 绘制决策边界 导入所需的库 import numpy as np %matplotlib widget import m…

【Linux】进程的认识

查看进程指令proc/ps/top 注意哦, 我们经常使用的指令, 像ls, touch…这些指令在启动之后本质上也是进程 proc 是内存文件系统, 存放着当前系统的实时进程信息. 每一个进程在系统中, 都会存在一个唯一的标识符(pid -> process id), 就如同学生在学校里有一个专门的学号一样…

Mac笔记本安装maven

Mac笔记本安装maven 一、通过brew安装maven 如果你的mac笔记本安装了homebrew可以使用如下命令安装 brew install maven安装完成后可以使用命令brew list maven来查看maven的安装位置 $ brew list maven /usr/local/Cellar/maven/3.6.3_1/bin/mvn /usr/local/Cellar/mave…

从零开始学Docker(三):DockerFile镜像定制

宿主机环境&#xff1a;RockyLinux 9 前言&#xff0c;定制docker镜像的方式有两种&#xff1a; 手动修改容器内容&#xff0c;然后docker commit提交容器为新的镜像通过在dockerfile中定义一系列的命令和参数构成的脚本&#xff0c;然后这些命令应用于基础镜像&#xff0c;依…

leetcode 面试题 0106.字符串压缩

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;面试题 0106.字符串压缩 思路&#xff1a; 开辟一个新的空间&#xff08;空间要大一点&#xff0c;因为可能压缩后的字符串比原字符串大&#xff09;&#xff0c;然后遍历原字符串统计当前字符的个数&#xff0c;再写入到…

使用vmd渲染并保存指定分辨率的图片

准备TCL脚本文件 # 设置渲染分辨率为1920x1080,600dpi render TachyonInternal out.tga width 1920 height 1080 dpi 600# 启用抗锯齿选项 display antialias on运行TCL 输入&#xff1a; source render.tcl使用Photoshop打开输出的out.tga文件并保存常用图片格式

【蓝桥杯备考资料】如何进入国赛?

目录 写在前面注意事项数组、字符串处理BigInteger日期问题DFS 2013年真题Java B组世纪末的星期马虎的算式振兴中华黄金连分数有理数类&#xff08;填空题&#xff09;三部排序&#xff08;填空题&#xff09;错误票据幸运数字带分数连号区间数 2014年真题蓝桥杯Java B组03猜字…

RK3568平台开发系列讲解(应用篇)输入设备应用编程

🚀返回专栏总目录 文章目录 一、输入设备介绍二、input 子系统三、读取输入设备四、按键应用编程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解输入设备应用编程。 一、输入设备介绍 输入设备是指可以接收用户输入的设备,例如键盘、鼠标、触摸屏等…

41. linux通过yum安装postgresql

文章目录 1.下载安装包2.关闭内置PostgreSQL模块:3.安装postgresql服务:4.初始化postgresql数据库:5.设置开机自启动:6.启动postgresql数据库7.查看postgresql进程8.通过netstat命令或者lsof 监听默认端口54329.使用find命令查找了一下postgresql.conf的配置位置10.修改postgre…

保姆级秋招教程之简历篇

大家好&#xff0c;我是千寻哥&#xff0c;个人简历在程序员求职过程中扮演着至关重要的角色。 今天我将详细给大家介绍一下写简历的必备要素和布局&#xff0c;同时强调应避免的“坑”&#xff01; 希望能通过这些技巧&#xff0c;能帮助程序员打造一份出色的简历&#xff0c;…

Python - print

文章目录 1. end‘’2. 未完待续~ 1. end‘’ 如果没有end‘’&#xff0c;每次print语句都会自动换行&#xff0c;而有了这个语句&#xff0c;数据就不会自动换行&#xff0c;而是在输出的数据后面加上空格&#xff08;空格数取决于引号里面的空格数&#xff09;示例&#xf…

Vulnhub: hacksudo: search靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.170 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.170 80端口目录爆破 feroxbuster -k -d 1 --url http://192.168.111.170 -w /opt/zidian/SecLists-2022.2/Discovery/Web…

计网 第三章错题整理 (3.4以后)

3.4 ③ 发送窗口不能大于接收窗口 否则窗口大小大于序号范围一般的时候 超时重传接收方没法辨别是新帧还是旧帧 本题有歧义 因为编号个数可以自定义的话&#xff0c;信道利用率都可达到百分之百 数据帧长度为128B的话 发送同样一个比特序列&#xff0c;需要更多的比特数 所以要…

7、单元测试--测试RestFul 接口

单元测试–测试RestFul 接口 – 测试用例类使用SpringBootTest(webEnvironment WebEnvironment.RANDOM_PORT)修饰。 – 测试用例类会接收容器依赖注入TestRestTemplate这个实例变量。 – 测试方法可通过TestRestTemplate来调用RESTful接口的方法。 测试用例应该定义在和被测…

【多模态】22、UniDetector | 检测开放世界中的一切!(CVPR2023)

文章目录 一、背景二、方法2.1 UniDetector 框架结构2.2 Heterogeneous Label Space Training2.3 open-world inference 三、效果3.1 数据集3.2 Object Detection in the Open World3.3 Object Detection in the Closed World3.4 Object Detection in the Wild3.5 Comparison w…

msf 渗透基础篇(基本命令)

一 Metasploit 目录结构 以 kali 为例&#xff0c;几个关键路径&#xff0c;熟悉一下&#xff0c;为了更方便的查找。 1、msf 的安装路径&#xff1a; ┌──(root㉿kali)-[/usr/share/metasploit-framework] └─# ls app documentation metasploit-framework.gemspec…