c++:继承(超详解)

2023-10-26

目录

一:什么是继承

二:继承的格式

继承的总结:

二:子类和父类(基类和派生类)

1.子类和父类的相互赋值:

2.同名的成员变量

3.同名成员函数

三:子类中默认的成员函数

1.构造函数

2.析构函数

3.拷贝构造

4.赋值运算符重载

 四:单继承和多继承

单继承:

 多继承:

菱形继承

解决方法一:

解决方法二:

单继承和多继承的总结:


一:什么是继承

定义:

继承(inheritance)机制是面向对象程序设计中使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生的新类,称派生类(或子类),被继承的类称基类(或父类)。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。之前接触的复用都是函数复用,继承是类设计层次的复用。

好吧,光看也看不出个啥,还是直接上代码吧

代码:

class human {//定义了一个父类,名字叫human
public:
	string name = "小明";//父类里面定义了一个string类型的和一个int类型
	int age = 18;
};
class student:public human {//定义了一个以public方式继承父类的子类student
public:
	int schoolnum = 666;//在父类的name和age的基础上增加了一个schoolnum
	void print()
	{
		cout << name << endl << age << endl << schoolnum << endl;//输出
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

结果如下:

 好吧是不是还是看不懂,那让我们把这个代码分成两半

第一部分:

class human {
public:
	string name = "小明";
	int age = 18;
};

第二部分:

class student:public human {
public:
	int schoolnum = 666;
	void print()
	{
		cout << name << endl << age << endl << schoolnum << endl;
	}
};

这样我们发现,其实第一部分的代码就是我们平时使用的class。

对于第二部分的解读我们先举一个例子

如果我们要设计一个学校系统,那么对于学生,老师.....一系列的人,我们都是需要将姓名和年龄等必要信息录入,但是单独针对到某一类人,比如学生,除了必要信息外,还单独有一个学号。比如老师除了必要信息外,还有一个单独的职工号。

 所以为了偷懒提高效率,我们这里就可以把姓名和年龄封装到一个class类里面,也就是我们第一部分的代码,然后再新创一个类,继承原有姓名和年龄类的基础上,再新增学号/职工号,也就是我们第二类。

所以说怎么继承呢?

二:继承的格式

class 新类的名字:继承方式 继承类的名字{};

以我刚才的例子为例

class student:public human{};
//student是新类的名字,public是继承方式,human是要继承的类
//意思就是说我定义了一个名叫 student的类 以public的方式 来继承你human

我们这里对于student和human就有两种叫法。

一种是教科书里面的基类(human)和派生类(student)。

我本人喜欢第二种父类(human)和子类(student)。毕竟感觉就像继承家产一样。

三:继承后的子类成员访问权限

这里我先丢一张图在,这是教科书里面老师铁定要求背诵的

在这里插入图片描述

 我这里分享一个很巧妙的方式

我们假设

public>protectd>private

我们取x和y中,两个较小的。

 最后一个private就都不可。这样我们就很轻松的记忆了下来

继承的总结:

1.基类private成员无论以什么方式继承到派生类中都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2.基类private成员在派生类中不能被访问,如果基类成员不想在派生类外直接被访问,但需要在派生类中访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.基类的私有成员在子类都是不可见;基类的其他成员在子类的访问方式就是访问限定符和继承方式中权限更小的那个(权限排序:public>protected>private)。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。

二:子类和父类(基类和派生类)

1.子类和父类的相互赋值:

代码:

class human {//父类
public:
	string name = "小明";
	int age = 18;
};
class student:public human {//子类
public:
	int schoolnum = 666;
};
int main()
{
	student st;
	human hm;
	hm = st;//将子类赋值给父类
	st = hm;//将父类赋值给子类
	return 0;
}

就会出现这样的结果:

 在这里我们引入一个叫做切片原则的东西

 因为父类中没有schoolnum,所以父类接收子类传过来的name和age之后,多余的schoolnum就不管了。但是如果父类传给子类,少传一个,所以会报错。

同时我们给出三种赋值方式

三种方式的赋值:

一:=符号

student st;//子类
	human hm;//父类
	hm = st;

 

二:引用

student st;//子类
	human& hm=st;父类

三:指针

student st;//子类
	human* hm=&st;//父类

2.同名的成员变量

在有些时候,父类和子类中出现了同一个成员变量,如下name

class human {
public:
	string name = "小明";
};
class student:public human {
public:
	string name = "小红";
	void print()
	{
		cout << name << endl;
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

这个时候编译器是以子类为优先

结果如下

但是如果我们就是想要访问父类的该成员变量,就需要加上修饰

void print()
	{
		cout << human::name << endl;
	}

 其实也很好理解,默认子类,父类就修饰限定

3.同名成员函数

如下,同样一个函数print在父类和子类中都存在

class human {
public:
	string name = "小明";
	void print()
	{
		cout << name << endl;
	}
};
class student :public human {
public:
	string name = "小红";
	void print()
	{
		cout << name << endl;
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

这就构成了隐藏。(函数重载是在同一个作用域,这里父类和子类是两个作用域)

函数的隐藏,编译器会默认调用子类中匹配的函数,如果没有编译器就会报错

上面的结果如下

虽然成员函数的隐藏,只需要函数名相同就构成隐藏,对参数列表没有要求。

 但是我们修改一下子类的函数

void print(int x)//我们对子类的print函数加入一个参数
	{
		cout << name << endl;
	}

 这是因为编译器默认调用子类中print函数,但是子类中唯一的print函数有一个默认的参数,所以编译器无法找到匹配的print函数,所以就会报错。

三:子类中默认的成员函数

1.构造函数

编译器会默认先调用父类的构造函数,再调用子类的构造函数,如下

class human {
public:
	human(string name = "小明")//先调用:父类默认构造调用一个print打印name
		:_name(name)
	{
		cout << name << endl;
	}
protected:
	string _name;
};


class student :public human {//后调用:子类默认构造调用一个print打印name和age
public:
	student(string name,int age)
		:_age(age)
	{
		cout << name << endl<<age<<endl;
	}
protected:
	int _age;
};


int main()
{
	student st("小红", 18);
	return 0;
}

 结果如下

 可以看到,编译器先调用了父类的,打印出了小明,然后再次调用了子类的打印出了小红和age。

所以说请务必保证父类构造有效,假如父类失效

human(string name)//你这里不传值,那么就不能完成初始化,相当于父类失效
		:_name(name)
	{
		cout << name << endl;
	}

那么就必须在子类中给父类构造赋值

student(string name,int age)
		:_age(age)
		, human(name)//新增,子类以自己的name给父类的析构中的name赋值,age和name的顺序随意变动

结果如下

 实在不行就把父类的构造删了,反正编译器也默认会生成的

2.析构函数

析构函数和构造函数相反,编译器默认先调用子类的析构函数,再调用父类的析构函数。

验证如下:

我们在原有的代码上,加入两个析构函数

class human {
public:
	human(string name = "小明")
		:_name(name)
	{}
	~human()
	{
		cout << "我是父类" << endl;
	}
protected:
	string _name;
};
class student :public human {
public:
	student(string name,int a = 20)
		:age(a)
	{}
	~student()
		
	{
		cout <<"我是子类"<< endl;
	}
protected:
	int age;
};
int main()
{
	student st("小明", 18);
	return 0;
}

结果如下:

 所以说

千万不要在子类中调用父类的析构

千万不要在子类中调用父类的析构

千万不要在子类中调用父类的析构

如果是指针类型,那么同一块区域被析构两次就会造成野指针的问题。

3.拷贝构造

子类中调用父类的拷贝构造时,直接传入子类对象即可,父类的拷贝构造会通过“切片”拿到父类的那一部分。

class human {
public:
	human(string name="小明")
		:_name(name)
	{
		cout << name << endl;
	}
protected:
	string _name;
};
class student:public human {
public:
	student(string name, int age)
		:_age(age)
	{
		cout << name << endl << age << endl;
	}
	student(student& s)
		:human(s)//直接将st传过来通过切片拿到父类中的值
		,_age(s._age)//拿除了父类之外的值
	{
		cout << s._age << endl<<s._name<<endl;
	}
protected:
	int _age;
};
int main()
{
	student st("小红",18);
	student st2(st);
	return 0;
}

结果如下:

4.赋值运算符重载

子类的operator=必须要显式调用父类的operator=完成父类的赋值。

因为子类和父类的运算符,编译器默认给与了同一个名字,所以构成了隐藏,所以每次调用=这个赋值运算符都会一直调用子类,会造成循环,所以这里的赋值要直接修饰限定父类

class human {
public:
	human(string name = "小明")
		:_name(name)
	{
	}
	human& operator=(const human& p)
	{
		if (this != &p)
		{
			cout << "调用父类" << endl;
			_name = p._name;
		}
		return *this;
	}
protected:
	string _name;
};
class student :public human {
public:
	student(string name, int age)
		:_age(age)
	{
	}
	student(student& s)
		:human(s)
		, _age(s._age)
	{
	}
	student& operator=(const student& s)
	{
		if (this != &s)
		{
			cout << "调用了子类" << endl;
			human::operator=(s);//必须调用父类运算符
			_age = s._age;
			_name = s._name;
		}
		return *this;
	}
protected:
	int _age;
};
int main()
{
	student st("小红", 18);
	student st2(st);
	student st3("小刚", 16);
	st = st3;
	return 0;
}

 结果如下:

 四:单继承和多继承

单继承:

一个子类只有一个直接父类的继承关系。

 多继承:

一个子类有两个或以上直接父类的继承关系。

 由以上两点,我们就会发现一个很蛋疼厉害的继承,

菱形继承

好,我们先上一段经典菱形继承代码 

这是个代码是有问题的

class A {
public:
	string name;
};
class B :public A {
public:
	int age;
};
class C :public A {
public:
	string sex;
};
class D :public B, public C {
public:
	int id;
};
int main()
{
	D student;
	student.name = "小明";
	student.age = 18;
	student.sex = "男";
	student.id = 666;
	return 0;
}

啪的一下,很快啊,报错就出来了 

 因为这里的name,同时存在B和C中,所以D不知道继承B的name还是C中的name

这也就是引出了代码冗余和二义性的问题。

所以我们有两种解决方法

解决方法一:

加修饰限定

student.B::name = "小明";

这里我们指定继承B中的name,就不会冲突了

解决方法二:

虚继承:在继承方式前加上virtual。

class B :virtual  public A {
public:
	int age;
};
class C :virtual public A {
public:
	string sex;
};

单继承和多继承的总结:

别用菱形继承就完了

多继承是C++复杂的一个体现。有了多继承,就存在菱形继承,为了解决菱形继承,又出现了菱形虚拟继承,其底层实现又很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++:继承(超详解) 的相关文章

  • 是否有与 SQL Server newsequentialid() 等效的 .NET

    我们使用 GUID 作为主键 您知道默认情况下它是集群的 将新行插入表中时 它将插入表中的随机页 因为 GUID 是随机的 这会对性能产生可衡量的影响 因为数据库始终会分割数据页 碎片 但我使用顺序 GUID 的主要原因是因为我希望将新行插
  • Microsoft Visual C++ 2008 和 R2007b 的 Mex 类型

    我想对 vs2008 和 matlab2007b 使用 mex 类型 我尝试了下面的代码 include
  • 从文本文件中读取所有内容 - C

    我正在尝试从文本文件中读取所有内容 这是我写的代码 include
  • float.Parse 不再在 Unity 中工作 (C#)

    我有一个包含以下代码行的工作项目 public InputField mass float val float Parse mass text 非常简单 用户输入一定量的质量 然后将其从文本解析为浮动 几天前这工作得很好 我什至能够多次导出
  • 可空引用类型意外 CS8629 可空值类型对于临时变量可能为空

    在 C 8 项目中 我使用可为 null 的引用类型 并收到意外的 或者至少对我来说意外的 CS8629 警告 bool singleContent x DataInt null bool multiContent x DataNvarch
  • 如何使构造函数只能由基类访问?

    如果我想要一个只能从子类访问的构造函数 我可以使用protected构造函数中的关键字 现在我想要相反的 我的子类应该有一个构造函数 该构造函数可以由其基类访问 但不能从任何其他类访问 这可能吗 这是我当前的代码 问题是子类有一个公共构造函
  • ASP.NET Core 测试 - 没有方法 'public static IHostBuilder CreateHostBuilder(string[] args)

    我正在尝试在测试中设置我的应用程序并在中使用Startup s Configure method context Database EnsureCreated 并期待着Sqlite文件出现在Test sbin文件夹 这是我的代码 using
  • EntityFramework:“参数值超出范围。”

    我在 EntityFramework 模型优先 中保存小数时遇到问题 在我的 EDMX 中 我声明我的属性为 Decimal 30 10 然后我尝试保存该数字 1215867935736100000 结果是 Parameter value
  • Python NET 调用具有返回值和输出参数的 C# 方法

    我有以下静态 C 方法 public static bool TryParse string s out double result 我想使用 Python NET 包从 Python 调用它 import clr from System
  • 使用正在运行的进程的共享内存收集核心转储

    核心转储仅收集进程空间 而不收集为进程间通信创建的共享内存 如何使核心转储也包含正在运行的进程的共享内存 设置核心文件过滤器 proc PID coredump filter per http man7 org linux man page
  • 在 Visual Studio 中调试时向后拖动指令指针

    如需演示 请查看 基本上 我知道这在 Visual Studio Community Edition 2015 中是可能的 我想知道 a 这与 Intellitrace 和 历史调试 有关吗 b 这样做会有副作用吗 或者这只是将指令向后移动
  • 策略模式的现实示例

    我一直在读关于OCP原理 http en wikipedia org wiki Open closed principle以及如何使用策略模式来实现这一目标 我打算尝试向几个人解释这一点 但我能想到的唯一例子是根据 订单 的状态使用不同的验
  • 如何将焦点设置到 Windows 窗体应用程序中的控件?

    在 Windows 窗体应用程序中 when我是否编写代码以在应用程序启动时以及随后调用函数后将焦点设置到控件 例如 如果我有一个 DropDownList 一个 TextBox 和四个按钮 并且我希望将 Focus 设置为 DropDow
  • 列表框显示类名称而不是值

    我正在开发一个项目 其中用户应该向动物输入值 名称 年龄 性别等 并且用户输入的值应该显示在列表框中 这些类相互继承 以下是继承的工作原理 Animalclass 是所有类的父类 Mammal类继承自Animal class Dog类继承自
  • ASP.NET 5 (vNext) - 配置

    我正在尝试学习 ASP NET 5 我在 Mac OS X 上使用它 此时 我有一个如下所示的 config json 文件 配置 json AppSettings Environment dev DbSettings AppConnect
  • 在 Qt C++ 中使用多个键

    我正在构建 坦克 游戏 我使用关键事件在地图上运行我的坦克 实际上我当时只能使用一把钥匙 但我需要有能力去完成任务 同时向上和离开 这是我的单键事件代码 switch event gt key case Qt Key Up if ui gt
  • 使用 STL 迭代器而不初始化它

    我想做这样的事情 container iterator it NULL switch eSomeEnum case Container1 it vecContainer1 begin break case Container2 it vec
  • 奇怪的 MSC 8.0 错误:“ESP 的值未在函数调用中正确保存...”

    我们最近尝试将一些 Visual Studio 项目分解为库 并且在测试项目中一切似乎都编译和构建得很好 其中一个库项目作为依赖项 然而 尝试运行该应用程序给我们带来了以下令人讨厌的运行时错误消息 运行时检查失败 0 ESP 的值未在函数调
  • 如何检查多个变量是否等于同一值?

    如何比较多个项目 例如 我希望检查所有变量 A B 和 C 是否都等于字符 X 或所有三个变量都等于 O 如果其中 2 个为 X 1 个为 O 则应返回 false I tried if A B C X A B C O Do whateve
  • 如果 foreach 是一个结构数组,它会复制每个元素吗?

    我有一个结构数组 做foreach运算符在迭代数组时复制每个元素 据我所理解foreach只是底层的语法糖转换为for 所以看来答案是否定的 但我很想得到一些确认 PS 看来应该有人已经问过了 但我无法轻易找到任何东西 因此 请以提供的参考

随机推荐

  • 史蒂夫科恩_科特琳·科恩范围

    史蒂夫科恩 学习Android开发 Learning Android Development If you are familiar with Dagger 2 you probably know that Scope an importa
  • STM32-I2C --- 通过IO口模拟

    I2C 通过IO口模拟 1 I2C介绍 I2C两根线 一根时钟线 SCL 一根信号线 SDA 数据传输时 时钟线信号为低电平时 数据线电平才允许变化 起始和停止位控制时 时钟线信号为高电平 数据线由高向低变化为起始信号 数据线由低向高变化为
  • NOT 函数

    前言 NOT 函数是用于对参数值求反的一种 Excel 函数 当要确保一个值不等于某一特定值时 可以使用 NOT 函数 简言之 就是当参数值为 TRUE 时 NOT 函数返回的结果恰与之相反 结果为 FALSE 比如 NOT 2 2 4 由
  • stm32-看门狗(独立看门狗,窗口看门狗)

    基于野火教程的看门狗 实验器材 stm32c8t6 LED灯 按键一个 实验一 独立看门狗 1 实验原理 2 实验代码讲解 3 实验现象 实验二 窗口看门狗 1 实验原理 2 实验代码讲解 3 实验现象 在进入正题之前 我们先了解一下什么是
  • Flutter组件 - Expanded

    Row Column Flex会被Expanded撑开 充满主轴可用空间 使用方式 Row children
  • C# 获得配置文件存储目录

    在C 中 不同工程为了读取自己的配置文件 由于系统当前目录的问题 往往在不同情况下 使用不同的方法 下面对在什么时候 使用什么方法 做一个整理 一下方法很多是引用别人信息 情况1 如果是一个标准的Win独立应用 或者一个标准的WEB独立应用
  • [Ubuntu]深度学习环境安装NVIDIA-1080+CUDA9.0+cuDnn+Tensorflow-gpu-1.6.0+conda

    1 安装Miniconda wget https mirrors tuna tsinghua edu cn anaconda miniconda Miniconda 1 6 0 Linux x86 64 sh bash Miniconda
  • AngularJS2.0 开发指南

    经过前面的学习 基本了解了Angular2 0的使用 所有的Module都是一个Component 甚至一个事件响应也是一个Component 或者表单验证也可以是一个Component Angular的运作机制图 Angular2 0 A
  • 面向对象高级3-内部类&枚举&泛型

    1 内部类 回顾 之前学了类的四个成员 分别是成员变量 成员方法 代码块 构造器 现在这是第五个成员 内部类 前三个作了解 第四个重点学习 内部类的应用场景 场景 当一个类的内部 包含了一个完整的事物 且这个事物没有必要单独设计时 就可以把
  • 路由中的mata

    一 定义 meta简单来说就是路由元信息 也就是每个路由身上携带的信息 二 使用 1 面包屑 path index name index meta keepAlive true 需要缓存 title 首页 components gt imp
  • linux 一个用户进入另外一个用户的家目录

    a userb use b 用户 cd home a 只有查看权限chmod 755 home a 转载于 https blog 51cto com wsxxsl 2096507
  • Python之区块链简单记账本实现

    在上一篇 Python之区块链入门 中讲述了区块链的基础知识 并用Python实现了区块和区块链的结构 在本篇中 将基于上面的内容实现一个简单的记账本功能 记账本的功能如下 实现基本的收支记录 计算当前余额 对收支情况做简单统计分析 账单记
  • android studio 导入module作为lib使用

    android studio 导入module作为lib使用 1 将 android module导入 android project 中 2 在要作为lib导入的module 的build gradle文件中添加一行 apply plug
  • 若依前后端分离版3、用户角色权限和动态菜单

    文章目录 一 用户角色和权限 1 前端 2 后端 一 用户角色和权限 1 前端 我们通过登陆 F12进行查看发现还有getinfo和getRouters方法 我们发现若依在页面跳转的时候都会出现这两个方法 这其实就是我们在路由里边配置的东西
  • 汽车智能座椅系统

    概述 自动驾驶领域日渐成熟 将催生一些新应用场景 如休闲 娱乐 社交和健康等 传统的座椅控制系统无法满足人们新的需求 更安全 更舒适 智能化及健康化体验将成为未来智能座椅的方向 恒润凭借汽车电子技术的积累 能够提供智能汽车座椅的解决方案 为
  • 笔记

    零散个人笔记 书籍已出版 完整版 淘宝 京东 当当有售 1 tensorflow源码完整下载方法 git clone recurse submodules https github com tensorflow tensorflow git
  • 作业 从外到内:一次完整的渗透测试!作业

    9th 一 环境准备 Windows10 1709地址 WindowsServer2016 x64 修改了密码 原密码 lonelyor org UbuntuServer2004 x64 UbuntuServer1604 x64 pfsen
  • Qt实现coturn穿透客户端,coturn服务器搭建

    目录 coturn简介 coturn服务器搭建 coturn服务验证 qt实现coturn穿透 NAT类型是否可以穿透 coturn简介 Coturn集成了stun turn协议 实现NAT检测 穿透就需要通过stun协议 NAT检测无法进
  • 渗透测试核心思路-边界突破

    概述 渗透测试的目标可以是单个主机 也可以是整个内网 在实战中 比如最近如火如荼的HW行动 更多的是对一个目标的内网进行渗透 争取获得所有有价值的资产 完整的内网渗透涉及的步骤如下图所示 我们总是先通过对外提供服务的 防守最薄弱的主机打进去
  • c++:继承(超详解)

    目录 一 什么是继承 二 继承的格式 继承的总结 二 子类和父类 基类和派生类 1 子类和父类的相互赋值 2 同名的成员变量 3 同名成员函数 三 子类中默认的成员函数 1 构造函数 2 析构函数 3 拷贝构造 4 赋值运算符重载 四 单继