从零开始的c++之旅——继承

1. 继承

1.继承概念及定义

        继承是面向对象编程的三大特点之一,它使得我们可以在原有类特性的基础之上,增加方法
        和属性,这样产生的新的类,称为派生类。

        继承 呈现了⾯向对象程序设计的层次结构,以前我们接触的函数层次的 复⽤,继承是类设计
        层次的复⽤。

        例如我们在实现老师的类teacher和学生的类student时,他们都有姓名/地址/ 电话/年龄等成员
        变量,都有identity⾝份认证的成员函数,设计到两个类⾥⾯就是冗余的。当然他们 也有⼀些
        不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣
        的独有成员函数是学习,⽼师的独有成员函数是授课。

class Student
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 
	void identity()
	{
		// ...
	}
	// 学习 
	void study()
	{
		// ...
	}
protected:
	string _name = "peter"; // 姓名 
	string _address; // 地址 
	string _tel; // 电话 
	int _age = 18; // 年龄 
	int _stuid; // 学号 
};


class Teacher
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 
	void identity()
	{
		// ...
	}
	// 授课 
	void teaching()
	{
		//...
	}
protected:
	string _name = "张三"; // 姓名 
	int _age = 18; // 年龄 
	string _address; // 地址 
	string _tel; // 电话 
	string _title; // 职称 
};

        我们可以将他们两个类中冗余的部分提取出来,实现一个新的类Person,就可以复用这些成
        员,不需要重新定义,省时省力。

class Person
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 
	void identity()
	{
		cout << "void identity()" << _name << endl;
	}
protected:
	string _name = "张三"; // 姓名 
	string _address; // 地址 
	string _tel; // 电话 
	int _age = 18; // 年龄 
};
class Student : public Person
{
public:
	// 学习 
	void study()
	{
		cout << Person::_name << endl;
	}
protected:
	int _stuid;// 学号
	string _name = "李四";
};
class Teacher : public Person
{
public:
	// 授课 
	void teaching()
	{
		//...
	}
protected:
	string title; // 职称 
	string _name = "王五";

};
int main()
{
	Student s;
	Teacher t;
	s.identity();
	t.identity();
	return 0;
}

1.2 继承定义

1.2.1 格式

如下图所示,Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。

1.2.2 继承方式及其特点

         1.如果我们不想让基类的对象被派生类访问,就将基类中的对象定义为private,这样无论是
            哪种继承方式,无论是在类内部还是外部都无法访问。

         2.如果想让基类的对象继承之后可以在派生类中被访问,但是不能在类外被访问,就定义成
            protect,这也是protect和private的区别,所以如果不涉及继承他们两个的作用是一致的

         3.有上表格我们可以总结出继承的规律:
            基类的私有成员在派⽣类都是不可⻅。
            基类的其他成员 在派⽣类的访问⽅式==Min(成员在基类的访问限定符,继承⽅式),
            public > protected > private。

         4.继承方式最好显示写出来,即使我们知道class默认private,struct默认public。

         5. 当然虽然c++中的继承方式相对复杂,但是我们实际当中基本都是使用public继承。
             也不提倡使用protetced/private继承,因为这两者继承下来的成员都只能在派⽣类的类⾥⾯
             使⽤,实 际中扩展维护性不强。

1.3 继承类模板

        在继承类模板的时候需要注意,当我们的基类是一个类模板的时候,我们使用其中的方法需
        要指定类域,不然编译器找不到对应的方法,因为模板的实例化是按需实例化,只有用了对
        饮的方法才对去实例化对应的方法,

	template<class T>
	class stack : public std::vector<T>
	{
	public:
		void push(const T& x)
		{ 
			vector<T>::push_back(x);
			//push_back(x);
		}
		void pop()
		{
			vector<T>::pop_back();
		}
		const T& top()
		{
			return vector<T>::back();
		}
		bool empty()
		{
			return vector<T>::empty();
		}
	};

2. 基类和派生类之间的转换

         public继承的 派生类对象 可以赋值给 基类的指针/基类的引用 。我们形象的将其称为切片。
        意思就是将派生类中的基类那部分切出来,基类指针只会指向派生类对象中基类有的那部分
        对象,下面为示例

class Person
{
protected :
 string _name; // 姓名 
 string _sex; // 性别 
 int _age; // 年龄 
};
class Student : public Person
{
public :
 int _No ; // 学号 
};
int main()
{
 Student sobj ;
 // 1.派⽣类对象可以赋值给基类的指针/引⽤ 
 Person* pp = &sobj;
 Person& rp = sobj;
 
 // ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的 
 Person pobj = sobj;
 
 //2.基类对象不能赋值给派⽣类对象,这⾥会编译报错 
 sobj = pobj;
 
 return 0;

 需要注意的是 Person& rp = sobj 这条语句在执行过程中并没有产生临时对象,而是直接赋值给了rp。

3. 继承中的作用域

3.1 隐藏规则:

        1. 在继承体系中基类和派生类都有独自的作用域。
        2. 若基类和派生类有同名成员,则派生类成员将会屏蔽基类对同名成员的直接访问,这种现
            象叫做隐藏。
        3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
        4. 住哟i在实际继承体系中最好不要定义同名成员。

3.2 注意事项

        函数重载和隐藏都要求同名函数,但是函数重载的行为是要求同一作用域,而隐藏的要求是
        两个类为派生类和基类中有函数同名

        如果我们要在派生类当中调用基类被隐藏的同名函数,就需要指定类域。

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

4个默认成员函数

        类有6个默认成员函数,我们主要讨论其中较为重要的4个。

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

	Student(const char* name, int num ,const string& address)
		: Person(name)//父类成员调用父类的构造函数,没有就在子类初始化列表显示写
		, _num(num)
		, _address(address)
	{
		cout << "Student()" << endl;
	}

    2. 派生类的拷贝函数必须调用基类的拷贝构造完成棋类的拷贝初始化。

	Student(const Student& s)
		: Person(s)//把基类看作一个整体
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

    3. 派生类的operator=必须调用基类的operator完成基类的复制。需要注意的是派生类的
        operator=会隐藏基类的operator等于,使用显示调用基类operrtor=需要指定类域

	Student& operator = (const Student& s)
	{
		if (this != &s)
		{
			// 构成隐藏,所以需要显⽰调⽤ 
			Person::operator =(s);//将基类对象看作整体
			_num = s._num;
		}
		return *this;
	}

     4. 派生类的析构函数会在被调用后自动取调用基类的析构函数,因为这样才能保证派 ⽣类对象
         先清理派⽣类成员再清理基类成员的顺序。因此我们显示写派生类的析构函数时候需要注意
         不用显示写基类的析构函数

	~Student()
	{
        //不用写基类的析构函数
	}

     5. 派⽣类对象初始化先调⽤基类构造再调派⽣类构造。

     6. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(后续的多态章节
         会讲解)。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数
         不加 virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。

要点总结


         不写,编译器默认生成的行为是什么?
         默认生成不符合我们需求,自己写,得怎么写?
 
         特点:子类中继承下来的父类成员当做一个整体对象


         构造:
                 默认:子类成员 内置类型(有缺省值就用,没有不确定)和自定义类型(默认构造) + 父类
                 成员(必须调用父类默认构造) 


         拷贝构造:
                子类成员 内置类型(值拷贝)和自定义类型(这个类型拷贝构造) + 父类成员(必须调用父类
                拷贝构造)

         赋值重载:
                类似拷贝构造

         析构:
                子类成员 内置类型(不处理)和自定义类型(调用他的析构) + 父类成员(调用他的析构)
                自己实现的话,注意不需要显示调用父类析构,子类析构函数结束后,会自动调用父类
                析构

4. 继承和友元

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

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;
}
int main()
{
	Person p;
	Student s;
	// 编译报错:error C2248: “Student::_stuNum”: ⽆法访问 protected 成员 
	// 解决⽅案:Display也变成Student 的友元即可 
	Display(p, s);

	return 0;
}

5. 继承和静态成员

基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都 只有⼀个static成员实例。

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;
	// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的 
	// 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份 
	cout << &p._name << endl;
	cout << &s._name << endl;
	// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的 
	// 说明派⽣类和基类共⽤同⼀份静态成员 
	cout << &p._count << endl;
	cout << &s._count << endl;
	// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员 
	cout << Person::_count << endl;
	cout << Student::_count << endl;
	return 0;
}

6. 多继承及其菱形继承问题

6.1 继承模型

        单继承: 一个派生类只有个一个直接的基类

        多继承: ⼀个派⽣类有两个或以上直接基类,多继承对象在内存中的模型 是,先继承的基类
                        在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。

        菱形继承: 是多继承的⼀种特殊情况

从上图得出,菱形继承数据冗余和二义性问题,在Assistant的对象中Person成员会有两份。⽀持多继承就 ⼀定会有菱形继承,所以实践中我们也是不建议 设计出菱形继承这样的模型的。

                        ​​​​​​​        ​​​​​​​        

6.1 虚继承

        在具有二义性的类前面加上关键字virtual,可以实现虚继承,解决菱形继承的问题,但是对于
        计算机来说会造成多余的新能损失

class Person
{
public:
	string _name; // 姓名 
	/*int _tel;
    int _age;
     string _gender;
     string _address;*/
     // ...
};

// 使⽤虚继承Person类 
class Student : virtual public Person
{
protected:
	int _num; //学号 
};

// 使⽤虚继承Person类 
class Teacher : virtual public Person
{
protected:
	int _id; // 职⼯编号 
};

// 教授助理 
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程 
};

int main()
{
	// 使⽤虚继承,可以解决数据冗余和⼆义性 
	Assistant a;
	a._name = "peter";
	return 0;
}

        很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形
        继承就有 菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱
        形继承。多继承可 以认为是C++的缺陷之⼀。

        我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤
        还是底层 都会复杂很多。

        虽然菱形继承会造成一些不必要的麻烦,但是一些底层的实现还是需要用上的。但对于小萌
        新来说还是只可远观不可亵玩

8. 继承和组合 

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

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

        继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩
        箱复⽤。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,基类的内部细节对派⽣类可⻅。继承
        ⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依 赖
        关系很强,耦合度⾼。

        对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获
        得。对 象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤因为对
        象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。组合类之间没有很强的依赖关 系,
        耦合度低。优先使⽤对象组合有助于你保持每个类被封装。

        优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好。不过
        也不太 那么绝对,类之间的关系就适合继承(is-a)那就⽤继承,另外要实现多态,也必须要继
        承。类之间的 关系既适合⽤继承(is-a)也适合组合(has-a),就⽤组合。

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

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

相关文章

正向解析和反向解析

正向解析 服务端&#xff1a; [rootlocalhost rhel]# vim /etc/named.conf [rootlocalhost named]# vim /var/named/named.openlab.com 客户端&#xff1a; [rootlocalhost rhel]# nslookup 反向解析 服务端&#xff1a; [rootlocalhost rhel]# vim /etc/named.conf [ro…

计算机网络:网络层 —— 路由信息协议 RIP

文章目录 路由选择协议动态路由协议路由信息协议 RIPRIP 的重要特点RIP的基本工作过程RIP的距离向量算法RIP存在的问题RIP版本和相关报文的封装 路由选择协议 因特网是全球最大的互联网&#xff0c;它所采取的路由选择协议具有以下三个主要特点&#xff1a; 自适应&#xff1a…

基于yolov5的输电线,电缆检测系统,支持图像检测,视频检测和实时摄像检测功能(pytorch框架,python源码)

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; yolov5&#xff0c;输电线(线缆)检测系统&#xff0c;系统既支持图像检测&#xff0c;也支持视频和摄像实时检测【pytorch框架】_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于yolov5的输…

删除WPS的智能识别目录

很烦&#xff0c;对吧 智能识别目录很垃圾&#xff0c;无法直接删除&#xff0c;如果你选择左边的目录&#xff0c;删除的话&#xff0c;会顺便把右边的正文也删除了。 那么如何只删除左边目录&#xff0c;保留右边的正文呢&#xff1f;只有一个办法&#xff1a; ctrlshiftC复…

客户端与微服务之间的桥梁---网关

当我们创建好了N多个微服务或者微服务的实例之后&#xff0c;每个服务暴露出不同的端口地址&#xff0c;一般对于客户端请求&#xff0c;只需要请求一个端口&#xff0c;要隔离客户端和微服务的直接关系&#xff0c;保证微服务的安全性和灵活性&#xff0c;避免敏感信息的泄露。…

构建您自己的 RAG 应用程序:使用 Ollama、Python 和 ChromaDB 在本地设置 LLM 的分步指南

在数据隐私至关重要的时代&#xff0c;建立自己的本地语言模型 &#xff08;LLM&#xff09; 为公司和个人都提供了至关重要的解决方案。本教程旨在指导您完成使用 Ollama、Python 3 和 ChromaDB 创建自定义聊天机器人的过程&#xff0c;所有这些机器人都托管在您的系统本地。以…

C++STL-deque、stack、queue、priority_queue

C教学总目录 deque、stack、queue、priority_queue 1、deque2、stack使用介绍3、stack实现4、queue使用介绍5、queue实现6、priority_queue使用介绍7、priority_queue实现8、反向迭代器 1、deque deque是双端队列&#xff0c;我们学习的队列是先进先出的(First in first out)&a…

【c++篇】:掌握vector基础知识--基本操作与使用全知道

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨个人主页&#xff1a;余辉zmh–CSDN博客 ✨文章所属专栏&#xff1a;c篇–CSDN博客 文章目录 前言一.vector的基本概念1.定义2.主要特性和优点 二.vector的基本操作…

如何建购物网站提升用户体验

在构建一个购物网站时&#xff0c;用户体验是至关重要的&#xff0c;它直接影响到顾客的满意度和转化率。为了提升用户体验&#xff0c;可以从以下几个方面入手。 首先&#xff0c;网站设计应简洁明了。确保导航栏清晰易懂&#xff0c;让用户在寻找商品时不会迷失。此外&#x…

勒索软件如何传播?

在本文中&#xff0c;我们将讨论勒索软件对企业的影响并解释这些攻击的具体传播方式。 我们还将提供可采取的切实步骤来保护您自己和您的企业免受这些不断上升的威胁。 勒索软件对小型企业的攻击日益增多 勒索软件仍然是全球各种规模企业的头号威胁。 小型企业数据泄露的成…

Claude 3.5 新功能 支持对 100 页的PDF 图像、图表和图形进行可视化分析

Claude 3.5 Sonnet发布PDF图像预览新功能&#xff0c;允许用户分析长度不超过100页的PDF中的视觉内容。 此功能使用户能够轻松上传文档并提取信息&#xff0c;特别适用于包含图表、图形和其他视觉元素的研究论文和技术文档。 视觉PDF分析&#xff1a;用户现在可以从包含各种视觉…

交换排序(冒泡/快排)

一 . 交换排序 交换排序基本思想 : 所谓交换 &#xff0c; 就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置 。 交换序列的特点是 &#xff1a; 将键值较大的记录向序列的尾部移动 &#xff0c; 键值较小的记录向序列的前部移动 1.1 冒泡排序 在前面中 …

【反射率】-- Lab 转换(excel)

系列文章目录 文章目录 系列文章目录前言一、CIE1.CIE 简介2.cie 1931标准色度匹配函数数据3.从CIE1931RGB到CIE1931 XYZ 二、Lab颜色空间的理解1.Lab色差公式怎么计算色差 三、D65光源Lab计算总结 前言 一、CIE 1.CIE 简介 CIE是由国际照明工程领域中光源制造、照明设计和光…

[ 问题解决篇 ] win11中本地组策略编辑器gpedit.msc打不开(gpedit.msc缺失)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

c语言素数优化,图解

方法① 2~m-1范围 整体思路就是&#xff0c;整数取余0就break&#xff0c;后续判断取余不为0的i次数&#xff0c;如果到头也就是i值溢出m-1 也就是最后一次循环i都没break&#xff0c;说明全部取余都不为0&#xff0c;贼为素数 尽头 i<m-1 等于号和-1可以抵消&#xff0c; …

跨境电商行业中的主数据有哪些?

在全球化和数字化的推动下&#xff0c;跨境电商行业正迎来前所未有的发展机遇。无论是品牌拓展国际市场还是小型卖家进入全球电商平台&#xff0c;跨境电商企业都需要面对海量数据的管理与整合。在这个行业中&#xff0c;主数据管理尤为重要&#xff0c;因为跨境电商涉及到复杂…

opencv - py_imgproc - py_grabcut GrabCut 算法提取前景

文章目录 使用 GrabCut 算法进行交互式前景提取目标理论演示 使用 GrabCut 算法进行交互式前景提取 目标 在本章中 我们将了解 GrabCut 算法如何提取图像中的前景我们将为此创建一个交互式应用程序。 理论 GrabCut 算法由英国剑桥微软研究院的 Carsten Rother、Vladimir K…

Android编译环境构建(二)(可用于物理机、虚拟机、容器化Jenkins环境)

文章目录 需求环境要求文件下载Gradle Version:7.5cmdline-tools至此普通物理环境的Android编译环境已部署完毕 部署maven(可选)Jenkins配置Android构建环境 说明&#xff1a; 物理环境&#xff1a;物理机、虚拟机等 容器化环境&#xff1a;docker等 需求 Gradle Version:7.5 …

Django安装

在终端创建django项目 1.查看自己的python版本 输入对应自己本机python的版本&#xff0c;列如我的是3.11.8 先再全局安装django依赖包 2.在控制窗口输入安装命令&#xff1a; pip3.11 install django 看到Successflully 说明我们就安装成功了 python的Scripts文件用于存…

【免费】跟网型逆变器小干扰稳定性分析与控制策略优化

目录 主要内容 模型研究 数学模型 2.小信号控制结构 3.仿真模型 结果一览 下载链接 主要内容 弱电网往往具有阻抗较大和短路比较小等特点&#xff0c;易导致系统不稳定&#xff0c;限制了功率传输能力。该仿真建立了弱电网下跟网型逆变器的小信号扰动状态空间模…