C++笔记12•面向对象之继承•

news2024/11/15 8:00:14

继承

1.继承的概念及定义

(1)概念:
继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在 持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象 程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用, 承是类设计层次的复用。
class Person
{
public:
 void Print()
 {
 cout << "name:" << _name << endl;
 cout << "age:" << _age << endl;
 }
protected:
 string _name = "peter"; // 姓名
 int _age = 18;  // 年龄
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
以看到变量的复用。调用Print可以看到成员函数的复用。
class Student : public Person
{
protected:
 int _stuid; // 学号
};

(2)定义:

上面看到的 Person 是父类,也称作基类。 Student 是子类,也称作派生类。
(3)继承关系和访问限定符
(4)继承基类成员访问方式的变化  (范围: public > protected > private

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

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

※ 基类对象不能赋值给派生类对象。
※ 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向 派生类对象 时才是安全的(否则可能存在越界访问)。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的 dynamic_cast 来进行识别后进行安全转换。(后面介绍)
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; // pp是指向派生类对象的指针(安全),这种情况转换时可以的。
    ps1->_No = 10;
    
    pp = &pobj;
    Student* ps2 = (Student*)pp; // pp是指向基类对象的指针(不安全),这种情况转换时虽然可以,但是会存在越界访问的问题
    ps2->_No = 10;
}

3.继承中的作用域 

(1). 在继承体系中 基类 派生类 都有 独立的作用域
(2). 子类和父类中有同名成员, 子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。 (在子类成员函数中,可以 使用 基类 :: 基类成员 可以进行显式访问 ),
注意:隐藏的作用域是不同的,一个在父类一个在子类,要和函数重载区分,函数重载是同一个作用域。
(3). 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
(4). 注意在实际中在 继承体系里 面最好 不要定义同名的成员
代码如下:
// 示例1.Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :
 string _name = "张五"; // 姓名
 int _num = 888;   // 身份证号
};
class Student : public Person
{
public:
 void Print()
 {
 cout<<" 姓名:"<<_name<< endl;
 cout<<" 身份证号:"<<Person::_num<< endl;
 cout<<" 学号:"<<_num<<endl;
 }
protected:
 int _num = 666; // 学号
};
void Test()
{
 Student s1;
 s1.Print();
};

//示例2
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
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 test1()
{
	B b;
	b.fun(10);//要想访问A中的fun()  要这样写 b.A::fun();
};

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

4.派生类的默认成员函数

默认成员函数讲解可以见: C++笔记3•类和对象2•-CSDN博客
(1). 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
(2). 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
(3). 派生类的 operator= 必须要调用基类的 operator= 完成基类的复制。
(4). 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
(5). 派生类对象初始化先调用基类构造再调派生类构造。
(6). 派生类对象析构清理先调用派生类析构再调基类的析构。
注意:这个地方和构造、拷贝构造不一样,不用在子类显式调用基类的析构函数,编译器会自动调用的。
(7). 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同 (后面介绍 ) 。那么编译器会对析构函数名进行特殊处理,处理成 destrutor() ,所以父类析构函数不加 virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。
代码示例:
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()
	{
		//Person::~Person();这个地方和构造、拷贝构造不一样,不用再显式调用基类(Person)的析构函数,编译器会自动调用的
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};
void test2()
{
	Student s1("Ann", 24);
	//Student s2(s1);
	//Student s3("Jone", 25);
	//s1 = s3;
}
int main()
{
	test2();
	return 0;
}

5.继承与友元

基类的友元不能被派生类继承:意思是基类友元不能访问子类私有和保护成员

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;//编译错误 基类的友元不能被派生类继承
}

6. 继承与静态成员

基类定义了 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 test4()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;//人数初始化
	cout << " 人数 :" << Person::_count << endl;
}

注意:上方出现了protected访问限定符

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

7.菱形继承及菱形虚拟继承  

(1).继承的分类:单继承/多继承/菱形继承(多继承的特殊情况)

菱形继承的问题:对象成员模型构造时,可以看出菱形继承有数据冗余和二义性的问题。如上方 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 test5()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	//a._name = "peter";//编译器 会报出 Assistant::"name"不明确
	
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

即解决二义性问题,又解决数据冗余问题:虚拟继承

如上面的继承关系,在 Student Teacher 的继承 Person 时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地 方去使用。代码如下:
class Person
{
public:
	string _name; // 姓名
};
//class Student : public Person
class Student : virtual public Person
{
protected:
	int _num; //学号
};
//class Teacher : public Person
class Teacher : virtual public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void test6()
{
	Assistant a;
	a._name = "Jone";//可以正常访问,即解决二义性问题,又解决数据冗余问题
}

虚继承原理:

这个Person同时属于Student和Teacher,那Student和Teacher如何去找到公共的Person呢?
这里是通过了Student和Teacher的两个指针,指 向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的Person,所以就可以只有一个名字了“Jone”了。

8.继承与组合 

(1).public 继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象。
(比如Teacher是person,Student是person,所以Student和Teacher都可以继承person)
(2).组合是一种 has-a 的关系。假设 B 组合了 A ,每个 B 对象中都有一个 A 对象。
(比如奔驰和轮胎的关系,不能说是奔驰是轮胎,只能说是轮胎属于奔驰车的组件,所以就是组合关系)
(3).优先使用对象组合,而不是类继承 。
原因是:要有 低耦合高内聚 的思想
   1)在继承方式中,基类的 内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
   2) 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被
封装。

   3)之所以实际尽量多去用组合,是因为组合的耦合度低,代码维护性好。但是继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系既可以用继承,又可以用组合,那就优先用组合。

组合代码示例:

   // Tire和Car构成has-a的关系
   
   class Tire{
   protected:
       string _brand = "Michelin";  // 品牌
       size_t _size = 20;         // 尺寸
   
   };
   
   class Car{
   protected:
   string _colour = "白色"; // 颜色
   string _num = "*****"; // 车牌号
    Tire _t; // 轮胎
   };

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

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

相关文章

【Linux】进程|进程的查看与管理|创建进程

目录 1️⃣ 进程的概念&#xff08;Process&#xff09; 1.什么是进程&#xff1f; 2.多进程管理 3. 描述进程-PCB 2️⃣ 进程的查看与管理 1.使用指令查看进程 2.通过系统调用函数查看pid 3.杀掉进程 4.ppid&#xff08;父进程id&#xff09; 3️⃣ 创建进程 fork(…

大模型面试:LLM+向量库的文档对话系统

面试题 1.1 为什么大模型需要外挂(向量)知识库&#xff1f;如何将外部知识注入大模型&#xff0c;最直接的方法&#xff1a;利用外部知识对大模型进行微调 回答 大模型需要外挂(向量)知识库的原因&#xff1a; 知识更新频率&#xff1a;大模型在训练时使用的知识是静态的&a…

关于喷墨打印:液滴喷射及基材影响的那些事儿

大家好&#xff0c;今天我们来探讨一篇关于液滴喷射在生物应用中相关知识的文章——《Understanding droplet jetting on varying substrate for biological applications》是发表于《International Journal of Bioprinting》。在生物打印领域&#xff0c;了解液滴在不同基材上…

做每日计划记录,有什么好的软件推荐2channel茅盾先生《春蚕》苏四嫂的过劳死茅盾的《秋收》主题和象征文学风格

目录 做每日计划记录&#xff0c;有什么好的软件推荐 2channel 茅盾先生《春蚕》 苏四嫂的过劳死 茅盾的《秋收》 主题和象征 文学风格 做每日计划记录&#xff0c;有什么好的软件推荐 做每日计划记录的软件有很多&#xff0c;下面是几款非常实用的推荐&#xff0c;适合…

基于yolov8的人员溺水检测告警监控系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的人员溺水检测告警监控系统是一种高效、智能的安全监控解决方案。该系统利用YOLOv8目标检测算法的先进性能&#xff0c;实现了对水域中人员溺水行为的精准识别与快速响应。 YOLOv8继承了YOLO系列算法的优点&#xff0c;如单次预测、速度快、精度高&a…

牛客周赛 Round 57(A,B,C,D,E,F,G)

比赛链接 官方题解 这场的题还不错&#xff0c;D是一个计算几何&#xff0c;考察到了一些线性代数里叉积的知识&#xff0c;E是爆搜打表&#xff0c;不太常规&#xff0c;所以做起来感觉还不错&#xff1f;FG偏简单&#xff0c;F是multiset线段树&#xff0c;G是很简单的构造…

C语言 | Leetcode C语言题解之第376题摆动序列

题目&#xff1a; 题解&#xff1a; int wiggleMaxLength(int* nums, int numsSize) {if (numsSize < 2) {return numsSize;}int prevdiff nums[1] - nums[0];int ret prevdiff ! 0 ? 2 : 1;for (int i 2; i < numsSize; i) {int diff nums[i] - nums[i - 1];if ((…

maxwell读取mysql binlog到kafka

下载地址 https://maxwells-daemon.io/解压压缩包 tar -zxvf maxwell-1.29.0.tar.gz -C /opt/module/初始化maxwell元数据库 在mysql中为maxwell创建一个数据库 复制 config.properties.example 文件 cp config.properties.example config.properties修改config.propertie…

分享一个基于node.js的宠物医院预约挂号系统vue宠物医院管理系统(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

JS基础之【各种运算符与运算符的优先级】

&#x1f680; 个人简介&#xff1a;某大型国企高级前端开发工程师&#xff0c;7年研发经验&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码…

Nvidia财报前夕:市场预期股价波动创纪录,AI芯片巨头引领市场热潮

引言&#xff1a;Nvidia财报前市场躁动&#xff0c;股价波动预期创新高 随着Nvidia&#xff08;英伟达&#xff09;即将发布其最新财报&#xff0c;全球投资者和交易员们正屏息以待。作为全球人工智能芯片市场的领军者&#xff0c;Nvidia的每一次财报都牵动着市场的神经。据分析…

基于SpringBoot的校园二手交易平台

1.项目介绍 基于SpringBoot实现的校园二手交易平台。 技术框架&#xff1a;SpringBoot2.0.0 MyBatis1.3.2 Mysql5.7 Bootstrap 运行环境&#xff1a;jdk8 IntelliJ IDEA maven3 后台首页、菜单管理、角色管理、用户管理、日志管理、数据备份、分类管理、物品管理、 求…

BMC解决方案丨服务器故障诊断与预测平台方案设计与实现

近日&#xff0c;OurBMC社区理事成员单位浪潮计算机科技有限公司基于开放原子开源大赛的成果梳理了一份成熟的可落地方案——《基于BMC技术的服务器故障诊断与预测平台方案设计与实现》。该方案为开放原子开源大赛的冠军之作&#xff0c;极大推动了社区产业化落地的发展和工作。…

怎么用AI做赚钱的表情包?我来教你!真能轻松变现?我们试了试。

最近看到一则新闻&#xff0c;一个小哥靠做微信表情包赚了几十万&#xff0c;这引起了我的兴趣。 这让我想到&#xff0c;无论是传统的利他思维&#xff0c;还是投资自己的头脑&#xff0c;或者是表情包这样的新兴赚钱方式&#xff0c;成功的路径从未改变。 利他思维&#xff…

SystemTap(stap)脚本举例 包括系统调用监控,函数执行时间 函数出参信息和信号捕捉脚本等

SystemTap 脚本举例 运行环境检查 要想使用SystemTap&#xff0c;需要依赖环境支持。可以执行如下命令查看stap是否已经安装。 stap -ve probe begin { log("hello world") exit () }对内核函数增加探针&#xff0c;需要debuginfo信息&#xff0c;可以通过如下步检…

PyTorch构建神经网络

【图书推荐】《PyTorch深度学习与企业级项目实战》-CSDN博客 《PyTorch深度学习与企业级项目实战&#xff08;人工智能技术丛书&#xff09;》(宋立桓&#xff0c;宋立林)【摘要 书评 试读】- 京东图书 (jd.com) 训练一个神经网络通常需要提供大量的数据&#xff0c;我们称之…

特殊类设计(5个)与类型转换

引子&#xff1a;在生活中我们经常有不同类的需求&#xff0c;因此我们有了特殊类的设计&#xff08;有很多种模式等&#xff09;。由于类型需求不同我们有了类型转换。今天我们就来略讲略讲一下这方面的知识。 特殊类设计&#xff08;5个&#xff09; 注意&#xff1a;关键字…

uniapp发布包app.json文件配置及发包上传注意事项

一、分包&#xff08;提示主包大小不小于1.5M&#xff09; 我的分包代码 二、未开启js压缩 操作:【必须】在工具「详情」-「本地设置」中开启「上传代码时自动压缩脚本文件」的设置 三、未开启组件懒注入&#xff08;按需注入&#xff09; 只需将代码"lazyCodeLoading&qu…

深度剖析:黑神化悟空的防御机制,为何成为破解难题?

深度剖析&#xff1a;黑神化悟空的防御机制&#xff0c;为何成为破解难题&#xff1f; 黑神话悟空还没发售就被破解&#xff1f; #国产游戏 #悟空 #西游记 推荐阅读&#xff1a; 全红婵抖音魅力无限&#xff0c;粉丝数量历史性突破1000万大关&#xff01; 奥运激情日&#…

【日记】今天实在太累了(436 字)

正文 今天的工作强度跟之前完全不是一个级别。能不能不要给我找这么多事做&#xff0c;我只想摸鱼摆烂。以后到下一个单位就说自己啥都不会好了&#xff0c;省得一天天全来找我。 忙碌程度上升了一个数量级&#xff0c;一天结束之后完全不想说话。 好想睡觉。 昨晚尝试完成年度…