C++ -- 继承

news2024/12/28 20:27:48

文章目录

  • 1. 继承的概念和定义
    • 1.1 概念
    • 1.2 定义
      • 1.2.1 定义格式
      • 1.2.2 继承基类成员访问方式的变化
  • 2. 基类和派生类对象赋值转换
  • 3. 继承中的作用域
  • 4. 派生类的默认成员函数
  • 5. 继承与友元
  • 6. 继承与静态成员
  • 7. 复杂的菱形继承及菱形虚拟继承
  • 8. 继承和组合

1. 继承的概念和定义

1.1 概念

继承机制是面向对象设计程序使得代码可以复用,它允许程序员在保持原有类特性的基础上进程扩展,增加功能,这样产生的新的类叫做派生类。简单来说继承是类设计层次的复用。

#include <iostream>
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();
	return 0;
}

通过观察,我们看到了Student类和Teacher类都复用了Person类。此时,这里的Student类和Teacher类都是派生类,也可以叫做子类,而Person类是基类,也可以叫做父类。

1.2 定义

1.2.1 定义格式

class 派生类名 : 继承方式 基类名 {}; 比如:class student : public Person{};

1.2.2 继承基类成员访问方式的变化

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  3. 基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

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

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

double d = 1.1;
int i = d; //这里会产生临时变量:引用赋值就必须带上const
const int& ri = d;
class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};

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

int main()
{
	Person p;
	Student s;
	p = s;
    
    Person& rp = s;
    
    Person* pp = &s;
	return 0;
}

这里派生类直接赋值给基类,按常理来说也会有中间变量的产生,但是这里并不是,这里是天然支持的,Student类实例化后继承Person类的成员,但是Student中有特有的成员:

理解: Person& rp = s;

理解:Person* pp = &s;

3. 继承中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域。
class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111; // 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl; //输出小李子
		cout << " 身份证号:" << Person::_num << endl; //输出111
		cout << " 学号:" << _num << endl; //输出999
	}
protected:
	int _num = 999; // 学号
};

int main()
{
	Student s;
	s.Print();
	return 0;
}
  1. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)。 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
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;
	}
};
int main()
{
	B b;
	b.fun(10);
	return 0;
}

上面两个fun并不是构成函数重载,函数重载的前提是必须在同一个作用域中,所以这里是隐藏关系。

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

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

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  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; // 姓名
};
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); //Person::operator=() --> 防止隐藏
			_num = s._num;
		}
		return *this;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};

int main()
{
	Student s1("jack", 18); 
	Student s2(s1);
	Student s3("rose", 17);
	s1 = s3;
	return 0;
}

5. 继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s); //无法访问Student类中的_stuNum
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;
}
int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

6. 继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个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; // 研究科目
};
int main()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl; //4
	cout << " 人数 :" << Student::_count << endl; //4
	cout << " 人数 :" << Graduate::_count << endl; //4
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl; //0
	return 0;
}

实现一个不能被继承的类:

class A
{
private:
	A()
	{}
};

class B : public A
{

};

int main() //把基类的构造方法封装
{
	B b;

	return 0;
}

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

  1. 单继承:一个子类只有一个直接父类时称这个继承关系为单继承
  1. 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
  1. 菱形继承

菱形继承造成两个问题:数据冗余(空间浪费)和二义性

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 a;
	a._name = "peter";
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy"; //这个用法生活中就不对,_name生活中只有身份证上的名字
	return 0;
}

怎么来解决数据冗余问题?

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; // 主修课程
};

int main()
{
	Assistant a;
	a._name = "peter";
	a.Student::_name = "baulo";
	a.Teacher::_name = "manco";
	cout << a._name << endl; //最终输出manco
	return 0;
}

为什么这里的virtual关键字可以解决数据冗余问题?

class A{public:int _a;};
class B : virtual public A{public: int _b;};
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;
}

关系

非虚继承

B和C类中都有_a,此时出现了二义性和数据冗余的问题。D这个类中只有 _d,其他的都是继承下来的。

虚继承

这里是通过偏移量来解决了二义性和数据冗余的问题。再来看B类中:

上述中的假设B* pB = &b; pB->_a;其实和上面D类中的图中的B* pB = &d; pB-> _a是一样的,都是通过偏移量找到这里的 _a。验证:

这里观察仔细的可以发现上面我们使用偏移量的方式导致使用空间更大了,这里当有很多对象的时候,这里使用虚继承的话类中的数据也只有一份,相比非虚继承就会空间使用很少。

注意:实际运用中不要出现菱形继承的情况

8. 继承和组合

//组合
class A
{};

class B
{
private:
	A _aa;
};
//继承
class C
{};
class D : public C
{};

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

继承相比组合耦合度更高。相比继承更应该使用组合,但是要按照场景来说。

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

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

相关文章

听歌无线耳机哪个品牌好?2023适合听歌的好音质蓝牙耳机推荐

现如今&#xff0c;不管是听歌、追剧或是玩游戏&#xff0c;不少人喜欢戴蓝牙耳机进行。因为蓝牙耳机的功能更丰富&#xff0c;连接方便&#xff0c;还摆脱了线的束缚&#xff0c;使用起来更方便。那么&#xff0c;听歌无线耳机哪个品牌好&#xff1f;针对这个问题&#xff0c;…

『造轮子』亿级短URL生成器的架构设计及源码分享

&#x1f4e3;读完这篇文章里你能收获到 了解博主的短链生成的架构设计思路学习不同的短链技术方案选择学习基于混淆的自增短URL算法了解博主造的轮子SuperShortLink短链开源项目感谢点赞收藏&#xff0c;避免下次找不到~ 文章目录一、需求分析1. 短链生成及访问需求2. 短链应…

Python+requests+unittest+excel接口自动化测试框架

一、框架结构&#xff1a; 工程目录 二、Case文件设计 三、基础包 base 3.1 封装get/post请求&#xff08;runmethon.py&#xff09; 1 import requests2 import json3 class RunMethod:4 def post_main(self,url,data,headerNone):5 res None6 if heade…

互联网医院源码|搭建互联网医院系统时运营需要哪些资质?

目前一线城市已经都有完善的医疗系统&#xff0c;人们对于线上问诊系统越来越熟悉&#xff0c;使用的人也越来越多&#xff0c;对于一些偏远的地区来说在线问诊平台有着更广泛的应用和意义&#xff0c;互联网医院开发实现了医疗资源共享的情况&#xff0c;打破了地域限制&#…

文献管理软件Endnote、Mendeley、Zotero比较及选择,Zotero基础使用技巧

引言 大家好&#xff0c;我是比特桃。日常开发的项目分为两种&#xff0c;一种是成熟化的工程项目&#xff0c;只需要与具体的业务紧密结合及应用&#xff0c;难点也比较偏向于软件工程或者互联网高并发的方向。这种项目我们通常不会选择去查文献去寻找问题的解决办法&#xf…

微信小程序开发 | 音乐小程序项目

音乐小程序项目3.1 开发前的准备3.1.1 项目展示3.1.2 项目分析3.1.3 项目初始化3.2 【任务1】标签页切换3.2.1 任务分析3.2.2 前导知识3.2.3 编写页面结构和样式3.2.4 实现标签页切换3.3 【任务2】音乐推荐3.3.1 任务分析3.3.2 前导知识3.3.3 内容区域滚动3.3.4 轮播图3.3.5 功…

15-721 chapter2 内存数据库

Background 随着时代的发展&#xff0c;DRAM可以容纳足够的便宜&#xff0c;容量也变大了。对于数据库来说&#xff0c;数据完全可以fit in memory&#xff0c;但同时面向disk的数据库架构不能很好的发挥这个特性 这张图是disk database的cpu instruction cost 想buffer pool…

使用PyG(PyTorch Geometric)实现基于图卷积神经网络(GCN)的节点分类任务

文章目录基本介绍PyTorch Geometric图卷积神经网络GCN节点分类任务实现Cora数据集搭建GCN模型训练与测试迭代并输出完整代码基本介绍 PyTorch Geometric PyG&#xff08;PyTorch Geometric&#xff09;是一个基于PyTorch的库&#xff0c;可以轻松编写和训练图神经网络&#x…

ChatGPT,开启人机交互新篇章

ChatGPT在世界掀起了生成式AI的热潮&#xff0c;2个月实现月活用户过亿&#xff0c;是人类有史以来突破1亿人用户最快的消费端互联网产品&#xff0c;打破了Tiktok9个月破亿用户的纪录。不少专家将其视为第四次工业革命&#xff0c;资本市场也贡献大量涨停。当第一波的热情消退…

Android 7.1 Toast修复之终极篇,进程不奔溃(包含apk和兼容外来dex插件)

修复android 7.1 Toast的篇章&#xff1a; 常规app通过ams lancet 字节编码处理&#xff1a;Android Lancet Aop 字节编码修复7.1系统Toast问题(WindowManager$BadTokenException)多渠道游戏app兼容性处理:Android 7.1 Toast修复之多渠道包动态使用Booster或者Lancet plugin …

在外web浏览器远程访问jupyter notebook服务器【内网穿透】

文章目录前言视频教程1. Python环境安装2. Jupyter 安装3. 启动Jupyter Notebook4. 远程访问4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口转载自远控源码文章&#xff1a;公网远程访问jupyter notebook【cpolar内网穿透】 前言 Jupyter Notebook&#xff0c;它是一个交…

未来城市的微小单元:滴滴即将量产无人车

汽车诞生之后就一直作为除了家庭与公司之外的「第三空间」存在&#xff0c;技术的脚步从未停止过开发汽车的更多可能。尤其无人驾驶技术的出现&#xff0c;进一步解放了驾驶者&#xff0c;也让人们对于这一能够自主移动的第三空间充满了想象。作为未来城市的微小组成单元&#…

( “树” 之 DFS) 226. 翻转二叉树 ——【Leetcode每日一题】

226. 翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;[…

ThreadLocal源码分析及内存泄漏

ThreadLocal原理分析及内存泄漏ThreadLocal的使用ThreadLocal原理set方法解析replaceStaleEntry方法解析expungeStaleEntry方法解析cleanSomeSlots方法解析case 1: 向前有脏数据&#xff0c;向后找到可覆盖的Entrycase 2: 向前有脏数据&#xff0c;向后未找到可覆盖的Entrycase…

吴恩达机器学习--线性回归

文章目录前言一、单变量线性回归1.导入必要的库2.读取数据3.绘制散点图4.划分数据5.定义模型函数6.定义损失函数7.求权重向量w7.1 梯度下降函数7.2 最小二乘法8.训练模型9.绘制预测曲线10.试试正则化11.绘制预测曲线12.试试sklearn库二、多变量线性回归1.导入库2.读取数据3.划分…

掌握高效绘制地图的利器——LeafletJs

文章目录前言一、leafletJs是什么&#xff1f;二、快速入门1、安装2、快速入门三、进阶学习1、Map 控件2、Marker 标记3、Popup 弹出窗口4、图层四、项目实战封装文件4.1 基础点位图4.2 行驶轨迹图前言 GIS 作为获取、存储、分析和管理地理空间数据的重要工具&#xff0c;用 G…

数据结构与算法一览(树、图、排序算法、搜索算法等)- Review

算法基础简介 - OI Wiki (oi-wiki.org) 文章目录1. 数据结构介绍1.1 什么是数据结构1.2 数据结构分类2. 链表、栈、队列&#xff1a;略3. 哈希表&#xff1a;略4. 树4.1 二叉树4.2 B 树与 B 树4.3 哈夫曼&#xff08;霍夫曼&#xff09;树&#xff1a;Huffman Tree4.4 线段树&a…

编辑文件/文件夹权限 - Win系统

前言 我们经常会遇到由于权限不够无法删除文件/文件夹的情况&#xff0c;解决方案一般是编辑文件/文件夹的权限&#xff0c;使当前账户拥有文件的完全控制权限&#xff0c;然后再进行删除&#xff0c;下文介绍操作步骤。 修改权限 查看用户权限 右键文件/文件夹&#xff0c;…

(函数指针) 指向函数的指针

函数指针- 指向函数的指针函数指针的声明和使用通过函数指针调用函数函数指针做参数函数指针数组函数指针的声明和使用 函数指针的声明格式&#xff1a; 返回值类型 (*函数指针名)(参数列表); 其中&#xff1a; *函数指针名 表示函数指针的名称返回值类型 则表示该指针所指向…

【Kubernetes】StatefulSet对象详解

文章目录简介1. StatefulSet对象的概述、作用及优点1.1 对比Deployment对象和StatefulSet对象1.2 以下是比较Deployment对象和StatefulSet对象的优缺点&#xff1a;2. StatefulSet对象的基础知识2.1 StatefulSet对象的定义2.1.1 下表为StatefulSet对象的定义及其属性&#xff1…