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

news2024/11/5 18:09:43

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),就⽤组合。

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

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

相关文章

用 Vue.js 打造炫酷的动态数字画廊:展示学生作品的创意之旅

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

在AI面试面前,你就不要再装了!

金九银十校招季&#xff0c;人在卷&#xff0c;AI也在卷。 AI面试所带来的影响&#xff0c;远非只是在求职招聘环节。对于AI面试的讨论&#xff0c;事关效率与公平&#xff0c;也指向了让不少打工人都“细思极恐”的话题&#xff1a;我们的组织管理是否正被AI重塑&#xff0c…

AUTOSAR CP MCAL微控制器抽象层介绍

AUTOSAR(Automotive Open System Architecture)即汽车开放系统架构,它将汽车电子控制单元(ECU)的软件底层做了一个标准的封装,使得开发者能够共用一套底层软件,并通过修改参数来匹配不同的硬件和应用层软件。AUTOSAR CP(Classic Platform)是AUTOSAR架构中的一个重要组…

yoloV5实战笔记—环境搭建(一)

一、安装miniconda 从清华源进行下载 https://mirrors.tuna.tsinghua.edu.cn/ 具体命令参考&#xff0c;注意修改pip国内镜像地址 https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/ 创建环境&#xff0c;指定python版本 conda create -n demo python3.9激活环境 conda acti…

【贪心】【可行范围内最大边界】SCNU习题 P25.跳跃游戏

算法思想&#xff1a; 每次迭代更新可行至的最大范围r(r必保证>原位置&#xff09;&#xff0c;至到迭代结束&#xff0c;若r>length of array则说明可以跳跃至此 #include <iostream> #include <vector> #include <string> #include <sstre…

29.2 golang实战项目log2metrics架构说明

本节重点介绍 : 需求分析流程说明log2metrics架构设计 架构图 需求分析 算qps 比如统计 nginx日志中code200的qps对应就是 每隔10秒grep一下日志文件 &#xff0c;用增量/时间差 算出qps 日志关键字告警 错误类型的关键字举例 如应用连接mysql报错dial mysql host error…

基于 Spring Boot 和 Vue 的门票销售创新系统

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

沃德校园助手系统源码

沃德校园助手系统&#xff0c;专为校园团队量身打造&#xff0c;提供了一站式的技术系统与运营解决方案&#xff0c;目前该系统已深度适配微信小程序平台&#xff0c;旨在助力校园团队轻松构建属于自己的线上助手平台。 在功能性方面&#xff0c;沃德校园助手系统涵盖了多种校…

Qt QCheckBox、QPushButton和QRadioButton详解

QCheckBox&#xff08;复选框&#xff09; 功能&#xff1a;QCheckBox用于创建一个复选框控件&#xff0c;允许用户从多个选项中选择多个。 属性&#xff1a; checkable&#xff1a;决定复选框是否可以被选中或取消选中。checked&#xff1a;表示复选框当前的选中状态&#…

DDR5内存售价暴降80%,终于到了无脑下手的时候

DDR5 内存刚面世那会儿&#xff0c;大家吐槽最多的便是频率低、延迟高、价格还死贵死贵。 前两年首批 DDR5 内存频率多集中在 4800、5200、5600MT/s 等入门水平。 延迟高、游戏性能不如高频 DDR4 内存的同时&#xff0c;单条 16G 售价普遍来到 1000 元开外&#xff0c;部分 3…

Jenkins找不到maven构建项目

有的可能没有出现maven这个选项 解决办法&#xff1a;需要安装Maven项目插件 输入​Maven Integration plugin​

基于大数据的热门旅游景点数据分析系统的设计与实现

作者主页&#xff1a;编程千纸鹤 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参…

计算机网络:网络层 —— 移动 IP 技术

文章目录 移动性对因特网应用的影响移动 IP 相关基本概念移动IP技术的基本工作原理代理发现与注册固定主机向移动主机发送IP数据报移动主机向固定主机发送IP数据报同址转交地址方式三角形路由问题 移动性对因特网应用的影响 我们列举如下三个应用场景说明移动性对因特网应用的…

ssm校园二手交易管理系统+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码看文章最下面 需要定制看文章最下面 目 录 1 绪论 1 1.1 选题背景 1 1.2 选题意义 1 1.3 研究内容 2 2 系统开发技术 3 2.1 MySQL数…

CCS下载安装(以12.3.0版本为例)

Code Composer Studio 是一个集成开发环境 (IDE)&#xff0c;简称CCS软件。支持 TI 的微控制器和嵌入式处理器产品的开发。Code Composer Studio 包含一整套用于开发和调试嵌入式应用程序的工具。 CCS9.3.0及以上版本不需要License文件&#xff0c;但是CCS旧版本比如CCS5.5.0需…

短视频时代的崛起:TikTok为何如此受欢迎?

在数字媒体日益发展的今天&#xff0c;短视频已成为一种主流的传播形式&#xff0c;而TikTok则是这一趋势的代表性平台。自2016年上线以来&#xff0c;TikTok迅速崛起&#xff0c;吸引了全球数亿用户的关注和参与。究竟是什么让这款应用如此受欢迎&#xff1f;以下是对TikTok成…

SAP财务凭证冲销接口代码实现

SAP提供了标准的BAPI(BAPI_ACC_DOCUMENT_REV_POST)进行凭证冲销,但是由于很多参考过程不可以用标准BAPI进行冲销,所以我们不得不采用其它的方式来进行冲销的实现。 该示例是采用三个功能模块来实现凭证冲销的接口程序: POSTING_INTERFACE_START 内部预订界面启动信息 POS…

Vagrant使用教程:创建CentOS 8虚拟机

目录 简介准备工作下载配置Vagrant修改环境变量创建VAGRANT_HOME环境变量修改virturalBox新建虚拟机文件的默认生成路径修改Vagrant配置支持VirtualBox7.1.x版本创建Vagrant文件添加镜像 初始化并开机初始化开发环境开机 其他配置项宿主机的交换目录修改虚拟机内存修改 访问方式…

虚拟机 Ubuntu 扩容

文章目录 一、Vmware 重新分配 Ubuntu 空间二、Ubuntu 扩容分区 一、Vmware 重新分配 Ubuntu 空间 先打开 Vmware &#xff0c;选择要重新分配空间的虚拟机 点击 编辑虚拟机设置 &#xff0c;再点击 硬盘 &#xff0c;再点击 扩展 选择预计扩展的空间&#xff0c;然后点击 扩展…

CSS网页布局综合练习(涵盖大多CSS知识点)

该综合练习就是为这个学校静态网页设置CSS样式&#xff0c;使其变成下面的模样 其基本骨架代码为&#xff1a; <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta name"viewport" content…