C++入门——“继承”

news2024/12/26 21:37:58

一、引入

面相对象的计算机语言有三大特性:“封装”、“继承”、“多态”。今天来讲解一下C++的一大重要特性——继承。

        通俗理解来讲,继承就和现实生活一样,子辈继承父辈的一些特性,C++中的继承也可以这样理解。它允许我们在保持原有类(父类/基类)特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称⼦类(派生类)。它的语法是:子类:继承方式 父类

        比如以下代码示例:

//动物
class animal
{
protected:
	int leg;//腿
	int skin;//皮肤
	int head;//头
	//......
};

//马,继承了动物类
class horse : public  animal
{
protected:
	int strong;//强壮
};

//驴,继承了动物类
class donkey : public animal
{
protected:
	int endurance;//耐力
};

        以上代码中,动物类是一个父类,而马类和驴类就是子类,他们继承了动物类的特点,并且子类拥有动物类的参数。

二、父类成员继承到子类后的访问限制和继承关系的关系

        父类在子类的继承方式遵循如下:

        细心的朋友就发现了如下规律:在继承关系和父类的访问限定符中,按照访问权限最小的那个限定符执行。其中,访问权限:public > protected > private。

        虽然父类的private成员无法在子类中使用,但这并不意味着该成员没有被继承到子类中。

        在struct类和class类的默认继承方式和他们的默认访问限定符一样,如果不显式的写出继承方式,struct默认是public继承,class默认是private继承。

三、父类和子类对象的赋值兼容转换

        在继承当中,公有继承(public)的子类的对象、指针、引用可以赋值给父类的对象、指针、引用。这种行为叫做切片(切割),代表将子类对象中属于父类的那一部分切割给父类。但是父类的对象不能赋值给子类的对象,其因为子类的对象的他值没有得到父类对象初始化。

        在父类指针或者引用指向子类对象的前提下,通过强制类型转换赋值给子类对象。

int main()
{
	 
	horse h;//子类对象
	animal* aptr = &h;//父类指针指向子类

	horse* hptr = (horse *)aptr;//强制类型转换赋值给子类对象

	return 0;

        但是在父类指针指向父类对象的时候,就不能通过强转赋值给子类对象:


int main()
{
	 
	animal a;//父类对象
	animal* aptr = &a;//父类指针指向父类

	horse* hptr = (horse *)a;//强制类型转换赋值给子类对象

	return 0;
}

以上代码在VS中会出现如下的报错:

四、隐藏

        父类和子类拥有独立的作用域,另外在子类中,如果出现了和父类成员及其变量名字一模一样的情况,那么语法上会自动隐藏掉父类的同名成员及其变量,即使参数不一样,也不会构成重载,如果想要使用父类的那个函数或者变量,就需要显式的写出父类中的成员变量:“父类类名::父类成员”来使用隐藏掉的父类成员。因此为了避免隐藏的发生,尽量不要在子类中定义和父类成员名字一样的新成员。

//动物
class animal
{
public:
	void print()
	{
		cout << "class animal" << endl;
	}
protected:
	int leg;//腿
	int skin;//皮肤
	int head;//头
	//......
};

//马
class horse : public  animal
{
public:
	void print()
	{
		cout << "class horse : public  animal" << endl;
	}

protected:
	int strong;//强壮
};

int main()
{
	 
	horse h;
	h.print();



	return 0;
}

调用的是子类的print:

        若要调用父类print,则需要指定类域:

int main()
{
	 
	horse h;
	h.animal::print();



	return 0;
}

运行结果如下:

五、子类的默认成员函数

        子类的默认成员函数和普通类大差不差,只是在调用子类的默认成员函数之前,都需要先调用父类的默认成员函数(除开析构函数)。

        1.构造函数

        如果父类没有默认的构造函数,就需要在子类的构造函数中显式的调用父类构造,如果父类有默认的构造函数,则不需要显式的调用。

父类有默认构造:

//动物
class animal
{
public:
	animal()
		:leg (1)
		,skin (2) 
		, head(3)
	{
		cout << "class animal" << endl;
	}


protected:
	int leg;//腿
	int skin;//皮肤
	int head;//头
	//......
};

//马
class horse : public  animal
{
public:
	horse()
		:strong(1)
	{
		cout << "class horse : public  animal" << endl;
	}
protected:
	int strong;//强壮
};


int main()
{
	 
	horse h;


	return 0;
}

父类没有默认构造:

//动物
class animal
{
public:
	animal(int l,int s,int h)
		:leg (l)
		,skin (s) 
		, head(h)
	{
		cout << "class animal" << endl;
	}


protected:
	int leg;//腿
	int skin;//皮肤
	int head;//头
	//......
};

//马
class horse : public  animal
{
public:
	horse()
		:animal(1,2,3)//显示调用父类的构造
		,strong(1)
	{
		cout << "class horse : public  animal" << endl;
	}
protected:
	int strong;//强壮
};


int main()
{
	 
	horse h;


	return 0;
}

运行结果:

        2.析构函数

        析构函数不用我们显式的调用,编译器会自动帮我们调用,不过调用的顺序是先调用子类的析构函数,再调用父类的析构函数,满足栈先进后出的特点。

//动物
class animal
{
public:
	~animal()
	{
		cout << "~animal()" << endl;
	}


protected:
	int leg;//腿
	int skin;//皮肤
	int head;//头
	//......
};

//马
class horse : public  animal
{
public:
	~horse()
	{
		cout << "~horse()" << endl;
	}
protected:
	int strong;//强壮
};

int main()
{
	 
	horse h;


	return 0;
}

运行结果:

        3.拷贝构造

        子类的拷贝构造需要显式调用父类的拷贝构造(拷贝构造也是一种构造),如果不显式调用,编译器将会报错。

//动物
class animal
{
public:
	animal()
	{}

	animal(const animal& copy)
	{
		leg = copy.leg;
		skin = copy.skin;
		head = copy.head;

		cout << "animal(const animal& copy)" << endl;
	}

protected:
	int leg;//腿
	int skin;//皮肤
	int head;//头
	//......
};

//马
class horse : public  animal
{
public:
	horse()
	{}

	horse(const horse& copy)
		:animal(copy)//显式调用父类拷贝构造,这里构成切片的条件
		, strong (copy.strong)
	{
		cout << "horse(const horse& copy)" << endl;
	}
protected:
	int strong;//强壮
};

int main()
{
	 
	horse h1;
	horse h2 = h1;


	return 0;
}

运行结果:

        值得注意的是:最好在初始化列表中显式的调用父类的拷贝构造,如果在子类的大括号里调用,比如:

	horse(const horse& copy)
	{
		animal(copy);
		strong = copy.strong;

		cout << "horse(const horse& copy)" << endl;
	}

        这样的方式是错误的,编译器会认为你定义了一个匿名对象,并重新定义了一个“copy”变量,这不仅违背了我们想调用拷贝构造的初心,还导致了重定义“copy”变量的错误。

        在大括号内部调用以下方式也是错误的:

animal::animal(copy);

       拷贝构造也是构造,类成员的变量是在初始化列表就已经被初始化,在初始化列表之后才是大括号内部的内容。并且基类的构造优先于派生类,因此以上方式达不到初始化基类的效果。

4.赋值运算符重载

        因为赋值运算符的函数名都是一样,所以在在子类的重载函数中需要指定类域:

//动物
class animal
{
public:
	animal& operator=(const animal& a)
	{
		leg = a.leg;
		skin = a.skin;
		head = a.skin;
		cout << "animal& operator=(const animal& a)" << endl;
		return *this;
	}

//protected:
	int leg;//腿
	int skin;//皮肤
	int head;//头
	//......
};

//马
class horse : public  animal
{
public:
	horse& operator=(const horse& a)
	{
		animal::operator=(a);
		cout << "horse& operator=(const horse& a)" << endl;
		return *this;
	}

protected:
	int strong;//强壮
};


int main()
{
	 
	horse h1;
	horse h2;
	h1.leg = 1000;
	h2 = h1;

	cout << h2.leg << endl;
	return 0;
}

运行结果:

六、多继承和钻石继承(菱形继承)

        C++的继承方式有两种——单继承,多继承,以上代码为单继承,马类和驴类又可以派生出骡子类,那么这个骡子类就是多继承。

//动物
class animal
{
public:
	animal& operator=(const animal& a)
	{
		leg = a.leg;
		skin = a.skin;
		head = a.skin;
		cout << "animal& operator=(const animal& a)" << endl;
		return *this;
	}

//protected:
	int leg;//腿
	int skin;//皮肤
	int head;//头
	//......
};

//马
class horse : public  animal
{
public:
	horse& operator=(const horse& a)
	{
		animal::operator=(a);
		cout << "horse& operator=(const horse& a)" << endl;
		return *this;
	}

//protected:
	int strong;//强壮
};

//驴
class donkey : public animal
{

protected:
	int endurance;//耐力
};

//骡子
class mule : public horse, public donkey
{
public:
	int hard_working;//努力工作
};

int main()
{
	mule m;


	cout <<23 << endl;
	return 0;
}

        多继承的父类,每一个类对应着一个继承方式,不可省略。

        多继承衍生出了一个继承方式“菱形继承”,马和驴都继承了动物类,那么驴继承了马类和驴类,那么我如果想访问驴类中的“head”,那么访问的是马类的head还是驴类的head呢?这种菱形继承在一定程度上浪费了空间,还会在访问上造成一定程度的歧义(编译器一般会报错)。

        解决菱形继承的方式有两种:一种是使用关键字“virtual”(虚拟的),即在驴类和马类继承的父类前加上这个关键字,这种继承方式叫“虚继承”,不过这种继承牺牲了一定的性能为代价如果发生了多个类的菱形继承,我们需要整理关系网,在继承了同一个父类的子类处加上该关键字。(不建议这种方式)

        第二种是非必要的情况下避免菱形继承,直接从源头避免这种继承方式。

补充:

        在类中的静态成员变量也会被继承到子类中,它和普通类一样无论实例化多少子类,都公用一个静态成员变量。

        父类的友元关系不会被继承。

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

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

相关文章

Windows C++控制台菜单库开发与源码展示

Windows C控制台菜单库 声明&#xff1a;演示视频&#xff1a;一、前言二、具体框架三、源码展示console_screen_set.hframeconsole_screen_frame_base.hconsole_screen_frame_char.hconsole_screen_frame_wchar_t.hconsole_screen_frame.h menuconsole_screen_menu_base.hcons…

html+css 实现hover 凹陷按钮

前言:哈喽,大家好,今天给大家分享html+css 绚丽效果!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、原理解析💡1.这是一个,hover时凹陷的效果。每个按钮是一个button…

【Android 远程数据库操作】

按正常情况下&#xff0c;前端不应该直接进行远程数据库操作&#xff0c;这不是一个明智的方式&#xff0c;应该是后端提供对应接口来处理&#xff0c;奈何公司各方面原因需要前端这样做。 对此&#xff0c;我对远程数据库操作做了总结&#xff0c;便于自己复盘&#xff0c;同…

机器学习第十四章-概率图模型

目录 14.1 隐马尔可夫模型 14.2马尔科夫随机场 14.3条件随机场 14.4学习与推断 14.4.1变量消去 14.4.2信念传播 14.5近似推断 14.5.1 MCMC采样 14.5.2 变分推断 14.6 话题模型 14.1 隐马尔可夫模型 概率围棋型是一类用图来表达变量相关关系的概率模型.它以图为表示工具…

Transformer(课程笔记)

一&#xff1a;Motivation RNN需要顺序的执行&#xff0c;不利于并行计算。 RNN的变体例如GRU、LSTM等需要依靠注意力机制解决信息瓶颈等问题。 抛弃RNN结构&#xff0c;提出了Transformer结构。 Transformer整体架构 二&#xff1a; 输入层&#xff08;BPE&#xff0c;PE&…

《黑神话:悟空》玩家必看!AMD显卡驱动24.8.1版全力支持!

系统之家于8月20日发出最新报道&#xff0c;AMD发布了最新的24.8.1版本驱动&#xff0c;本次更新增加了《黑神话&#xff1a;悟空》《星球大战&#xff1a;亡命之徒》等游戏的支持&#xff0c;且HYPR Tune支持允许HYPR-RX启用游戏内技术。下面跟随小编一起来看看AMD显卡驱动24.…

Centos7 message日志因dockerd、kubelet、warpdrive、containerd等应用迅速增长

问题&#xff1a;公司服务器在部署一套业务后&#xff0c;message日志记录大量的dockerd、kubelet、warpdrive、containerd应用日志&#xff0c;每天增加2G大小的日志 解决方案&#xff1a; 前期吐槽下&#xff1a;发现某个帖子&#xff0c;需要会员或者花钱才能看&#xff0c…

探索网络安全的深度与广度:挑战、策略与未来展望

一、引言 在当今数字化的时代&#xff0c;网络已经成为社会运转的核心基础设施之一。从个人的日常通信、娱乐到企业的业务运营、国家的关键服务&#xff0c;几乎所有领域都依赖于网络。然而&#xff0c;随着网络的普及和应用的深化&#xff0c;网络安全问题也日益凸显&#xf…

松下弧焊机器人维修 控制柜故障 连接线修复

一、Panasonic焊接机器人控制柜与机器人的接线 机器人的控制箱&#xff0c;一定要配对使用。松下焊接机器人控制柜已经记忆了机器人的绝对原点(机器人位置控制原点)。 二、编码器电缆 (圆形连接器) 1. 接口的插头插座要注意&#xff0c;插头要插到插座中。 2. 用一手握住电缆&a…

网络原理TCP/UDP详解

目录 传输属的几种格式 1.xml&#xff1a;通过成对的标签表示键值对信息。 2.json&#xff1a;当前更主流一点的&#xff0c;网络通信的数据格式 3.yml&#xff08;yaml&#xff09;强制要求数据的组织格式 4.google protobuffer 传输层 1.端口号&#xff1a; UDP协议 …

Vue3 组件管理 12 种神仙写法,灵活使用才能提高效率

SFC 单文件组件 顾名思义&#xff0c;就是一个.vue文件只写一个组件 模板写法 如果这个组件想要在别的组件里使用&#xff0c;就需要在另一个.vue中引入和使用、复用 h函数写法 使用 defineComponent h 去进行组件编写 JSX/TSX写法 使用 defineComponent JSX/TSX 去进行…

【html+css 绚丽Loading】 - 000008 三才虚空灵瞳

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

应用方案 | 降压型DC-DC电源管理电路 D2576介绍

概述 D2576是一款高效降压型DC-DC转换器&#xff0c;固定52KHz开关频率&#xff0c;可以提供最高3A输出电流能力&#xff0c;具有低纹波&#xff0c;出色的线性调整率与负载调整率特点。 D2576内置固定频率振荡器与频率补偿电路&#xff0c;简化了电路设计。PWM控制环路可以调节…

Rivian暂停亚马逊送货车生产,特斯拉Roadster再引关注

Rivian遭遇供应链挑战&#xff0c;暂停亚马逊送货车生产 电动汽车制造商Rivian近期宣布&#xff0c;由于零部件短缺&#xff0c;已暂停为零售巨头亚马逊生产商业送货车。这一决定标志着Rivian在应对供应链挑战方面遭遇了最新挫折。作为Rivian的最大投资者&#xff0c;亚马逊持有…

画板444

p31 画H1和H2的封装 立创里面这次有尺寸了没单位好像 给的1.02 他设的1.1焊盘可以大点 排针穿过去的&#xff08;别的焊盘也这样&#xff1f;&#xff09; 引脚编号 直接改成2.54 他焊盘直间的 距离了 刚才改成通用的直径了&#x1f605;&#x1f605;&#x1f605; 这能测尺寸…

金九银十,软件测试面试八股文【含答案+文档】

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 参考答案&#xff1a; 兼容测试主要是检查软件在不同的硬件平台、软件…

google seo基础宝典,新手必学

Google SEO 是什么&#xff1f; Google SEO是指针对谷歌搜索引擎优化网站排名的一种营销方式&#xff0c;旨在提升外贸网站在谷歌的品牌和产品曝光度&#xff0c;从而吸引外贸订单。具体做法是根据谷歌的搜索引擎排名规则&#xff0c;对网站的内容、结构、链接等方面进行优化&a…

C++竞赛初阶L1-13-第五单元-循环嵌套(29~30课)536: T456455 画矩形

题目内容 根据输入的四个参数&#xff1a;a,b,c,f 参数&#xff0c;画出对应的矩形。 前两个参数 a,b 为整数&#xff0c;依次代表矩形的高和宽&#xff1b; 第三个参数 c 是一个字符&#xff0c;表示用来填充的矩形符号&#xff1b; 第四个参数 f 为整数&#xff0c;0 代表…

测试资料2222

一 解决穷举场景&#xff1a;使用等价类划分法 适用场景 正向用例&#xff1a;一条尽可能覆盖多条 逆向用例&#xff1a;每一条数据&#xff0c;都是一条单独用例 完整的用例应该是等价类和边界值一块写 二 能对限定边界规则设计测试点 2.1选取正好等于、刚好大于、刚好小于…

搜维尔科技:【产品推荐】Manus Quantum Mocap Metagloves VR手套数据手套机械手训练专用手套

全新量子追踪技术 Quantum Mocap Metagloves通过使用毫米级精确的指尖跟踪传感器来提供高保真手指跟踪。传感器没有漂移&#xff0c;可提供高度准确且可靠的手指捕获数据。 手指追踪的新黄金标准 Quantum Mocap Metagloves使用精确的量子跟踪技术捕捉手部每一个细节动作。让您…