c++:面向对象三大特性--继承

在这里插入图片描述

面向对象三大特性--继承

  • 一、继承的概念及定义
    • (一)概念
    • (二)继承格式
      • 1、继承方式
      • 2、格式写法
      • 3、派生类继承后访问方式的变化
    • (三)普通类继承
    • (四)类模板继承
  • 二、基类和派生类的转换
    • (一)基类转换派生类
    • (二)派生类转换基类
  • 三、几个重要细节
    • (一)继承与作用域
      • 1、作用域
      • 2、隐藏
    • (二)继承与友元
    • (三)继承与静态成员
  • 四、继承中派生类的构造函数
  • 五、多继承与菱形继承
    • (一)多继承
      • 多继承的指针偏移问题
    • (二)菱形继承
    • (三)虚继承
  • 六、继承和组合
    • 结束语:

一、继承的概念及定义

(一)概念

继承是⾯向对象程序设计使代码可以复用的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类。

(二)继承格式

1、继承方式

我们前面对类的成员有三种限制方式,这里也就对应了三种继承方式
在这里插入图片描述

2、格式写法

在这里插入图片描述

3、派生类继承后访问方式的变化

1、通过表格可以发现,如果是private成员,那么无论哪种继承方式都不可以访问到这个权限
2、此外,structclass这两个关键字在继承时也有差距,struct默认继承方式为公有,而class默认继承方式为私有。

我们如果将权限的大小定义为 public > protected > private, 那么其余访问方式变化就是将大于该继承方式的权限降到继承方式的权限即可

在这里插入图片描述

(三)普通类继承

这里用到的是继承最基本的语法,采用public继承,那么除了父类的private变量不可访问以外,成员的权限保持不变。

class Person
{
public:
	void Print()
	{
		cout << _name << endl;
		cout << _age << endl;
	}
protected:
	string _name = "张三"; // 姓名
private:
	int _age = 18; // 年龄
};

class Student : public Person
{
public:
	void func()
	{
		Print();
	}
protected:
	int _stunum; // 学号
};

(四)类模板继承

在之前我们实现stack时,采用的是新建了一个容器类型,在这里我们亦可以采用继承的方式来实现。
需要注意的是,派生类在继承时,如果需要访问父类的成员函数,需要指定类域,模板的成员函数采用的是按需实例化

namespace wgm
{
	template<class T>
	class stack : public std::vector<T>
	{
	public:
		void push(const T& x)
		{
			// 基类是类模板时,需要指定⼀下类域,
			// 否则编译报错:error C3861: “push_back”: 找不到标识符
			// 因为stack<int>实例化时,也实例化vector<int>了
			// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
			vector<T>::push_back(x);
		}
		void pop()
		{
			vector<T>::pop_back();
		}
		const T& top()
		{
			return vector<T>::back();
		}
		bool empty()
		{
			return vector<T>::empty();
		}
	};
}

二、基类和派生类的转换

(一)基类转换派生类

1、基类对象不能赋值给派⽣类对象。
2、基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针
是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型,可以使⽤RTTI(Run-Time Type Information)dynamic_cast 来进⾏识别后进⾏安全转换。

(二)派生类转换基类

1、public继承的派⽣类对象 可以赋值给基类的指针/基类的引⽤。这⾥有个形象的说法叫切⽚或者切
。寓意把派⽣类中基类那部分切出来,基类指针或引⽤指向的是派⽣类中切出来的基类那部分。

值得注意的是,之前在隐式类型转换时会生成临时变量,因此在应用时需要加上const,而在切片时不会生成中间的临时变量

class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
public:
	int _age = 18; // 年龄
};

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

int main()
{
	string s1 = "11111";
	const string& s2 = "11111";

	Student sobj;
	// 赋值兼容转换,特殊处理
	// 1.派生类对象可以赋值给基类的指针/引用
	Person* pp = &sobj;
	Person& rp = sobj;
	rp._age++;
	return 0;
}

在这里插入图片描述

接下来通过下面的例子发现,继承后的基类私有变量虽然访问不到,但是我们可以发现它在派生类的对象中依旧占据相应的空间,而经过赋值兼容转换变量的大小为基类的大小

在这里插入图片描述

在这里插入图片描述

接下来更加深层的来了解赋值兼容,发现基类的指针或引用在调用重名函数的时候,调用的是父类的函数,而派生类调用时因为隐藏的特点,派生类对象调用的是派生类的函数

class A
{
public:
	void func()
	{
		cout << "A::func()" << endl;
	}

protected:
	int _a;
	int _b;
private:
	int _c;
};

class B : public A
{

public:
	void func()
	{
		cout << "B::func()" << endl;
	}

public:
	int _d;
};

int main() {
	B obj_b;
	A* ptr_a = &obj_b;
	A& ref_a = obj_b;
	obj_b.func();
	ptr_a->func();
	ref_a.func();
	return 0;
}

2、子类的变量可以复制给父类。

	Person pobj = sobj;

在这里插入图片描述

三、几个重要细节

(一)继承与作用域

1、作用域

在继承体系中基类和派⽣类都有独⽴的作⽤域。

2、隐藏

派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏(在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问

需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

(二)继承与友元

在继承时,友元关系是不接受继承的。所以如果友元函数需要访问派生类的成员,需要重新声明友元。

(三)继承与静态成员

在继承后,静态成员变量始终只有基类在定义的这一份。通过下面的代码可以发现,我们可以用类域加静态变量的方式来访问静态变量,但是打印的地址是同一份。

class Person
{
public:
	string _name;
	static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _stuNum;
};
int main()
{
	Person p;
	Student s;
	cout << &p._count << endl;
	cout << &s._count << endl;
	cout << Person::_count << endl;
	cout << Student::_count << endl;
	return 0;
}

在这里插入图片描述

四、继承中派生类的构造函数

  1. 派⽣类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。基类没有默认的构造函数必须在派⽣类构造函数的初始化列表阶段显⽰调⽤
  2. 派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化。
  3. 派⽣类的operator=必须要调⽤基类的operator=。需要注意的是派⽣类的operator=隐藏了基类的operator=,所以指定基类作⽤域显⽰调⽤基类的operator=
  4. 派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。才能保证先清理派⽣类成员再清理基类成员。因为多态中⼀些场景析构函数需要构成重写。,那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系
class Person
{
public:
	Person(const char* name)
		: _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;
	}

	// destructor()
	~Person()
	{
		cout << "~Person()" << endl;
	}

protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	Student(int num, const char* address, const char* name)
		:_num(num)
		, _address(address)
		, Person(name)
	{
		cout << "Student()" << endl;
	}

	Student(const Student& s)
		: Person(s)
		, _num(s._num)
		, _address(s._address)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			_num = s._num;
			_address = s._address;
			Person::operator=(s);
		}

		return *this;
	}

	// destructor()
	~Student()
	{
		// 不需要写,子类析构函数结束后,会自动调用父类析构
		//Person::~Person();
		cout << "~Student()" << endl;
	}

protected:
	int _num; //学号
	string _address;
};

五、多继承与菱形继承

(一)多继承

单继承:⼀个派⽣类只有⼀个直接基类时称为单继承
多继承:⼀个派⽣类有两个或以上直接基类时称为多继承

多继承的指针偏移问题

多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯
在这里插入图片描述
通过上面的例子,我们可以清晰的认识到基类在派生类的储存情况。

(二)菱形继承

菱形继承:菱形继承是多继承的⼀种特殊情况,有数据冗余和⼆义性的问题

在这里插入图片描述

class Person
{
public:
	string _name; // 姓名
};

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

class Teacher : public Person
{
protected:
	int _id; // 职工编号
};

//给类加上 virtual 关键字,解决菱形继承造成的二义性和数据冗余。

//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 obj;
	obj.Student::_name = "张三";
	obj.Teacher::_name = "李四";

	return 0;
}

在这里插入图片描述
通过调试窗口,可以发现我们在继承时同时继承了来自Person和来自Teacher_name我们在写代码时无法处理这个二义性,同时也形成了数据冗余。

(三)虚继承

为了解决这个现象,我们只需要在继承同一个基类成员的派生类加上一个virtual关键字,底层会自行加工,使得我们后面访问的_name只是一份数据。

class Person
{
public:
	string _name; // 姓名
};

//给类加上 virtual 关键字,解决菱形继承造成的二义性和数据冗余。
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 obj;
	obj.Student::_name = "张三";
	obj.Teacher::_name = "李四";

	return 0;
}

下列窗口显示出来的_name实则是同一份数据,最开始指定类域Student::初始化_name为张三
在这里插入图片描述
我们通过Teacher::修改数据为李四,那么数据被修改为李四。
在这里插入图片描述

切记,尽量不用使用菱形继承,因为virtual关键字在解决问题的同时造成了效率的降低,代价有点大。

六、继承和组合

继承组合
定义public继承是⼀种is-a的关系。也就是说每个派⽣类对象都是⼀个基类对象。组合是⼀种has-a的关系。假设B组合了A,每个B对象中都有⼀个A对象。
复用方式白箱复用:在继承⽅式中,基类的内部细节对派⽣类可⻅⿊箱复⽤:通过调用对象的接口实现,对象的内部细节是不可⻅的
耦合度继承⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强,耦合度高组合类之间没有很强的依赖关系,耦合度低

我们可以发现,组合的好处要大于继承,在两种都可以的情况下,优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好。

结束语:

感谢一直以来支持的朋友,支持一路走来披荆斩棘的道友,或许不识,一路同行!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构 (12)串的存储实现

一、顺序存储结构 顺序存储结构是用一组连续的存储单元来存储串中的字符序列。这种存储方式类似于线性表的顺序存储结构&#xff0c;但串的存储对象仅限于字符。顺序存储结构又可以分为定长顺序存储和堆分配存储两种方式。 定长顺序存储&#xff1a; 使用静态数组存储&#xff…

在线绘制Nature Communication同款双色、四色火山图,突出感兴趣的基因

导读&#xff1a;火山图通常使用三种颜色分别表示显著上调&#xff0c;显著下调和不显著。通过为特定的数据点添加另一种颜色&#xff0c;可以创建双色或四色火山图&#xff0c;从而更直观地突出感兴趣的数据点。 《Nature Communication》文章“Molecular and functional land…

2024赣ctf-web -wp

1.你到底多想要flag??? 首先来解决第一关&#xff1a; 先了解一下stripos&#xff08;&#xff09;&#xff1b; 并且此函数处理数组返回false。而且pre_match同样遇见数组是返回false&#xff08;解释一下正则 i&#xff1a;这是正则表达式的修饰符&#xff0c;代表“不区…

计算机毕业设计Python+大模型美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

Linux 查看内核日志的方法

文章目录 1. dmesg 命令一. 介绍内核环形缓冲区的特点 二. 主要功能三. dmesg 使用 2. 查看kmsg文件/dev/kmsg 的用途使用 /dev/kmsg与 dmesg 的关系 3. 内核日志消息的打印行为 1. dmesg 命令 一. 介绍 dmesg&#xff08;display message 或 display driver message 的缩写&…

Perforce SAST专家详解:自动驾驶汽车的安全与技术挑战,Klocwork、Helix QAC等静态代码分析成必备合规性工具

自动驾驶汽车安全吗&#xff1f;现代汽车的软件包含1亿多行代码&#xff0c;支持许多不同的功能&#xff0c;如巡航控制、速度辅助和泊车摄像头。而且&#xff0c;这些嵌入式系统中的代码只会越来越复杂。 随着未来汽车的互联程度越来越高&#xff0c;这一趋势还将继续。汽车越…

从Full-Text Search全文检索到RAG检索增强

从Full-Text Search全文检索到RAG检索增强 时光飞逝&#xff0c;转眼间六年过去了&#xff0c;六年前铁蛋优化单表千万级数据查询性能的场景依然历历在目&#xff0c;铁蛋也从最开始做CRUD转行去了大数据平台开发&#xff0c;混迹包装开源的业务&#xff0c;机缘巧合下做了实时…

LLM PPT Translator

LLM PPT Translator 引言Github 地址UI PreviewTranslated Result Samples 引言 周末开发了1个PowerPoint文档翻译工具&#xff0c;上传PowerPoint文档&#xff0c;指定想翻译的目标语言&#xff0c;通过LLM的能力将文档翻译成目标语言的文档。 Github 地址 https://github.…

【踩坑】git中文乱码问题

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 背景说明 使用git diff显示中文乱码&#xff0c;如&#xff1a; 修复方法 执行一次&#xff1a; export LESSCHARSETutf-8 如果需要下次登录免输入…

安装Docker报错TCP connection reset by peer或者Timeout

原因&#xff1a;访问的外网下载导致超时或者断连接报错 修改为国内阿里下载地址 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

Linux宝塔部署wordpress网站更换服务器IP后无法访问管理后台和打开网站页面显示错乱

一、背景&#xff1a; wordpress网站搬家&#xff0c;更换服务器IP后&#xff0c;如果没有域名时&#xff0c;使用服务器IP地址无法访问管理后台和打开网站页面显示错乱。 二、解决方法如下&#xff1a; 1.wordpress搬家后&#xff0c;在新服务器上&#xff0c;新建站点时&am…

Rust Newtype模式(通过结构体封装现有类型来创建新的类型)(单字段结构体,通过.0访问)模式匹配、解构、DerefMut

文章目录 深入理解Rust中的Newtype模式什么是Newtype模式&#xff1f;Newtype模式的基本形式Newtype的访问访问 Newtype 的值1. 通过 .0 访问字段2. 通过方法访问3. 通过模式匹配&#xff08;解构&#xff09;访问 总结 Newtype模式的应用场景1. 类型安全2. 增强可读性3. 定制化…

【ArcGIS Pro】实现一下完美的坐标点标注

在CAD里利用湘源可以很快点出一个完美的坐标点标注。 但是在ArcGIS Pro中要实现这个效果却并不容易。 虽然有点标题党&#xff0c;这里就尽量在ArcGIS Pro中实现一下。 01 标注实现方法 首先是准备工作&#xff0c;准备一个点要素图层&#xff0c;包含xy坐标字段。 在地图框…

【ArcGIS Pro实操第10期】统计某个shp文件中不同区域内的站点数

统计某个shp文件中不同区域内的站点数 方法 1&#xff1a;使用“空间连接 (Spatial Join)”工具方法 2&#xff1a;使用“点计数 (Point Count)”工具方法 3&#xff1a;通过“选择 (Select by Location)”统计方法 4&#xff1a;通过“Python 脚本 (ArcPy)”实现参考 在 ArcGI…

学习threejs,使用设置lightMap光照贴图创建阴影效果

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.MeshLambertMaterial…

Cocos编辑器

1、下载 下载地址&#xff1a;https://www.cocos.com/creator-download 2、编辑器界面介绍 官方链接&#xff1a;https://docs.cocos.com/creator/3.8/manual/zh/editor/ 3、项目结构 官方链接&#xff1a;https://docs.cocos.com/creator/3.8/manual/zh/getting-started/…

C++11特性(详解)

目录 1.C11简介 2.列表初始化 3.声明 1.auto 2.decltype 3.nullptr 4.范围for循环 5.智能指针 6.STL的一些变化 7.右值引用和移动语义 1.左值引用和右值引用 2.左值引用和右值引用的比较 3.右值引用的使用场景和意义 4.右值引用引用左值及其一些更深入的使用场景分…

Notepad++ 替换所有数字给数字加单引号

前言 今天遇到这样一个场景&#xff1a; 要去更新某张表里 code1,2,3,4,5,6 的数据&#xff0c;把它的 name 设置为 ‘张三’ 但是 code在数据库里面的字段类型是 vachar(64)&#xff0c;它自身携带索引 原本可以这样写 SQL: update tableA set namezhangsan where code in …

Django 路由层

1. 路由基础概念 URLconf (URL 配置)&#xff1a;Django 的路由系统是基于 urls.py 文件定义的。路径匹配&#xff1a;通过模式匹配 URL&#xff0c;并将请求传递给对应的视图处理函数。命名路由&#xff1a;每个路由可以定义一个名称&#xff0c;用于反向解析。 2. 基本路由配…

工作中可以用到的前端小知识(不定时更新)

1、split 结合 filter(Boolean)使用&#xff0c;可以过滤空字符 2、分割 Unicode 字符 用 Array.from() 实现 const text "&#x1f44d;&#x1f60a;&#x1f468;‍&#x1f469;‍&#x1f466;"; const result Array.from(text); console.log(result); // 输…