【C++】继承

2023-10-26


需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。


 目录

一、继承的概念

二、被继承成员访问方式的变化

三、赋值兼容规则(切片) 

四、继承中的作用域

五、子类的默认成员函数

1、父、子类中各自的成员处理方式

2、需要自己写默认成员函数的情况

3、子类中默认成员函数的写法

3.1父类没有默认构造函数,需要在子类的构造函数里补充

3.2在子类中显式写拷贝构造

3.3在子类中显式写赋值运算符重载

3.4不需要显式调用析构函数

六、继承和友元、静态成员的关系

七、菱形继承和菱形的虚拟继承

1、菱形继承

2、二义性和数据冗余

3、虚拟继承解决二义性和数据冗余

4、virtual关键字解决二义性和数据冗余的方法

4.1未使用virtual关键字

4.2使用virtual关键字

4.3虚继承+重写问题

八、继承和组合的区别

1、组合的使用场景

2、继承和组合的区别


一、继承的概念

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

        现在有学生类和老师类,类中均有年龄、性别等相同的属性,这些相同的属性在每个类中都写一份就会出现代码的冗余。可以使用一个父类包含这些共有的成员变量和成员函数,让学生类、老师类作为子类对父类进行继承即可。

二、被继承成员访问方式的变化

public继承

protected继承

private继承

父类的public成员

public

protected

private

父类的protected成员

protected

protected

private

父类的private成员

子类不可见

子类不可见

子类不可见

        1、父类的private成员被子类继承后是不可见的。不可见指的是父类的private成员会被继承到子类,但是子类无论是在类里面还是类外面,都无法对这些被继承的私有成员进行访问。(但是可以使用继承的“获取成员变量的偷家函数”对这些不可见的成员变量进行修改、访问)

        2、除了父类中的private成员,其他成员被子类继承后最终的访问方式取决于该成员在父类中的权限和继承权限两者权限较小的那个。

        3、可以看出protected限定符是因为继承才出现的。保护和私有在当前类中没有区别,但子类继承时,父类中的私有成员子类是不可见的,而保护成员是可见的。

        4、在实际中一般使用都是public继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。父类一般是公有和保护,不使用私有

        5、class默认私有继承,struct默认公有继承。但好习惯是写明继承方式。

struct Student : public person
{

};
struct Teacher :person//不提倡,最好写明继承方式
{
};

三、赋值兼容规则(切片) 

class Person
{
protected:
	string _name;
	string _sex;
	int _age;
};
class Student : public Person
{
public:
	int _num;//学号
};
int main()
{
	Person p;
	Student s;
	//父类=子类 赋值兼容/切割/切片
	p = s;//父类对象
	Person* ptr = &s;//父类的指针
	Person& ref = s;//父类的引用
	return 0;
}

        1、子类对象可以赋值给父类对象父类的指针父类的引用。这种操作叫做赋值兼容/切割/切片,意为将子类对象中继承于父类的成员切割下来赋值给父类对象。这不是类型转换,是天然的赋值行为。(切片仅限公有继承举例:父类为公有,子类保护或私有继承后,成员变为保护和私有,子类再切片给父类,那么被继承的成员权限会变,所以切片仅限公有继承)

        2、只能将子类对象赋值给父类,父类对象不能给子类赋值。但是指针和引用却可以,不过存在越界风险。

int main()
{
	Person p;
	Student s;
	//s = (Student)p;//父类对象无法赋值给子类,强转也不行
	Student* ptr = (Student*)& p;//类型强转,有越界风险
	Student& ref = (Student&)p;//类型强转,有越界风险
	return 0;
}

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

四、继承中的作用域

        1、在继承中父类和子类都有自己独立的类域。

        2、当子类和父类中存在同名成员时,子类将会屏蔽继承于父类的同名成员,这种情况被称为隐藏或重定义。(子类内部优先使用自己类域的同名成员,外部可使用stu.Person::_name进行显示访问)

        3、子类和父类中的同名成员函数并不构成函数重载,因为它们所处于不同的类域,子类会隐藏父类同名函数。

        4、父类和子类尽量不要使用同名成员。

五、子类的默认成员函数

1、父、子类中各自的成员处理方式

        子类中有两部分成员,一类是子类原生的成员,另一类是继承于父类的成员。

        对于原生成员,按照普通类调用默认成员函数的规则进行处理;对于继承于父类的成员,将会调用父类中的默认成员函数进行处理。(各管各的)

2、需要自己写默认成员函数的情况

        1、父类没有默认构造函数,需要自己显式写构造。

        2、子类存在浅拷贝问题,需要自己显式写拷贝构造和赋值。

        3、子类有资源需要释放,需要自己写显式析构。

3、子类中默认成员函数的写法

3.1父类没有默认构造函数,需要在子类的构造函数里补充

class Person
{
public:
	Person(const char* name)
		: _name(name)
	{}
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	Student(const char* name = "张三", int num = 10)
		: Person(name)//必须调用父类的构造函数进行初始化
		, _num(num)
	{}
protected:
	int _num; //学号
};

        父类有提供默认构造函数就可以不用在子类写了。

3.2在子类中显式写拷贝构造

class Person
{
public:
	Person(const Person& p)//形参:引用切片对象
		: _name(p._name)
	{}
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	Student(const Student& s)
		:Person(s)//切片
		,_num(s._num)
	{}
protected:
	int _num; //学号
};

        利用切片传入父类对象构造父类。Student s1(s2),在初始化列表中,利用s2中的父类成员去拷贝构造s1中的父类成员。

3.3在子类中显式写赋值运算符重载

class Person
{
public:
	Person& operator=(const Person& p)
	{
		if (this != &p)
			_name = p._name;
		return *this;
	}
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	Student& operator=(const Student& s)
	{
		if (this != &s)//防止自己给自己赋值
		{
			Person::operator=(s);//切片传入父类赋值运算符重载中
			//根据子类成员进行深浅拷贝
			_num = s._num;
		}
		return *this;
	}
protected:
	int _num; //学号
};

        在子类赋值运算符重载中调用父类赋值运算符重载,通过切片,完成父类成员的赋值。

3.4不需要显式调用析构函数

错误代码:

~Student()
{
    Person::~Person();
}
//子类析构函数结束后会调用一次父类的析构函数

        ~Person前必须加类域Person。因为析构函数的名字会被编译器统一处理为destructor(),子类的析构函数和父类的析构函数之间构成隐藏,所以这里需要写明类域。

        但是,我们并不需要显式调用父类的析构函数,因为出了子类析构函数的作用域,编译器会自动调用父类的析构。手动调用父类析构将会造成重复析构。

六、继承和友元、静态成员的关系

        1、友元关系不能被继承

        2、父类中的静态成员也会被继承,但是整个继承关系中共用这个静态成员

七、菱形继承和菱形的虚拟继承

1、菱形继承

        从上图可以看出,Assistant中会有两份Person成员,调用时存在二义性和数据冗余。

2、二义性和数据冗余

int main()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	//a._name = "peter";//不能这么写,因为a中有两个_name成员,需要指定类域
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
	return 0;
}

        由于Assistant中有两个_name成员,直接调用存在二义性,需要在成员之前指定类域。

        _name这个成员变量有两个问题不大,毕竟一个人可以叫蒋灵瑜,在其他场合也可以叫小蒋。但如果这个成员变量是一个int _arr[50000]的数组呢,一个类中同时有两份这么大的数组,将会导致数据冗余。

3、虚拟继承解决二义性和数据冗余

        产生二义性和数据冗余的本质就是子类继承了多份相同成员。

        解决方法是在“腰部”类增加virtual关键字。

class Person
{
public:
	string _name; // 姓名
};
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; // 主修课程
};

4、virtual关键字解决二义性和数据冗余的方法

        先来一段菱形继承的代码,_a、_b、_c、_d分别是类A、类B、类C、类D中的原生成员。        

class A
{
public:
	int _a;
};
 class B : public A
//class B : virtual public A
{
public:
	int _b;
};
 class C : public A
//class C : virtual public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d._b = 2;
	d.C::_a = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

4.1未使用virtual关键字

通过调用内存,会发现对象d中存在两份的_a,存在二义性和数据冗余。

4.2使用virtual关键字

        当使用了虚拟继承,通过调用内存,发现对象d中仅有一份_a,但是继承于B类和C类的_b和_c上方多了一串地址,再次要通过内存查找这串地址,发现这串地址之后的位置存放一个数字0x14,这个数字就是继承于B的成员到_a的偏移量,通过这个偏移量,对象d便能到d.B::_a。这样就解决了菱形继承成员冗余的问题。

        这里的A叫做虚基类,在对象d中,将虚基类的成员放到一个公共的位置,继承的B、C类需要找到A的成员,通过虚基表中的偏移量进行计算。我们看到虚基表中第一行还空置了4个字节,这块空间存放的也是一个偏移量,它用于寻找d对象中的虚函数指针表。

        实际使用时,尽量不要使用用菱形继承,因为它本质就是C++设计的一个坑!

4.3虚继承+重写问题

        1、如果A类中还存在一个虚函数,那么对象d会在_a后面存放虚函数指针表;

        2、如果A类中存在一个虚函数,并且B、C类均对这个虚函数进行了重写,那么D类中必须对这个函数进行重写,否则将会发生虚函数表重命名的问题。

八、继承和组合的区别

        组合也是一种类复用的手段。

1、组合的使用场景

        适用组合的代码:轮胎和车的关系

class Tire
{
protected:
   string _brand = "Michelin";  // 品牌
   size_t _size = 18;         // 尺寸
};
   
class Car{
protected:
    string _colour = "白色"; // 颜色
    string _num = "xxxxx"; // 车牌号
    Tire _t; // 轮胎
};  

2、继承和组合的区别

public继承是一种is-a的关系,每个子类对象都是一个父类对象,例如“学生”“人”(子类学生,父类人)

组合是一种has-a的关系,B组合了A,每个B对象中都有一个A,例如“车”包含“轮胎”

如果两个类既可以是is-a,又可以是has-a的关系,那么优先使用组合

        继承是一种白盒复用,父类内部的细节对子类可见,破坏了封装。子类将会继承父类的公有和保护成员,一旦父类修改了这些成员的实现,将会影响子类的功能。子类和父类之间的依赖关系强,耦合度高。

        组合是一种黑盒复用,父类内部的细节对子类不可见,子类仅可使用父类的公有成员,只要父类的公有成员的实现细节不变,子类影响较小。父子之间没有很强的依赖关系,耦合度较低。

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

【C++】继承 的相关文章

  • 在 C 中从数组创建子数组的最佳方法

    我有一个数组说a 3 1 2 5 我必须创建另一个数组a2 2 2 5 我尝试过的是创建一个新数组a2 只需复制所需位置范围内的所有元素即可 在C语言中还有其他方法可以实现这一点吗 memcpy a2 a 1 2 sizeof a
  • std::ostream 需要功能帮助

    我需要有人逐步向我解释这些代码行 并且我需要一些帮助来使用 ostream 和简单的示例 谢谢 inline std ostream operator lt lt std ostream os const Telegram t os lt
  • 无法打开作为链接添加的 configSource 文件

    在我的 MVC 应用程序中 我使用外部配置文件来保持干净的 web config 有些文件很常见 我将它们作为链接从一个位置添加到项目中 对于这些文件 我将 复制 选项设置为 始终复制 这些文件将被复制到目标文件夹 我会看到它们 但是当我尝
  • 如何使用 C# 从数据库中检索多个图像

    我有一个包含9张图像的数据库 这些图像不断变化 所以我无法直接设置src在 HTML 中 img 标签来显示 9 个图像 我必须从数据库中选择它们并相应地绑定它们 我可以使用以下命令检索并打印 1 张图像Response BinaryWri
  • 如何在 servicestack.net 中实现身份验证

    我正在调查 servicestack net 但它的示例和文章似乎没有涵盖身份验证 这是由 servicestack net 处理的东西 如果是的话如何处理 我特别有兴趣实现对以下方面的支持 OAuth 因此能够检查原始请求并验证它 检索关
  • 将文本文件与 C# 中的文本模式进行比较?

    我将文本文件与类似的模式进行了比较 它正在将整行写入日志 如下所示 insert into depdb fin quick code met 但我需要单独写这个depdb或者depdb fin quick code met 即 我只需要这个
  • INotifyPropertyChanged 与线程

    我有一个 BindingList
  • 使用 istream_iterator 范围构造时无法访问向量

    我尝试编译此代码片段 但出现编译器错误 使用 Visual Studio 2010 进行编译 include
  • BigInteger 乘以 Double

    我的物理老师给全班布置了一项艰巨的任务 我正在尝试创建一个程序来为我计算一些事情 在某个时刻 我需要将分子数量乘以百分比 Ulong 不能容纳大到 6022 10 19 的数字 所以我必须使用 net 4 0 中的 BigInteger 但
  • 如何从函数调用事件处理程序?

    我有一个类 我从中调用一个函数ABC string st 带字符串参数 该函数定义在一个Form class Form1 我有一个列表视图 想要从函数中自动调用列表视图 mouse click 事件 我该如何做到这一点 您不能调用另一个类的
  • __syncthreads() 死锁

    如果只有部分线程执行 syncthreads 会导致死锁吗 我有一个这样的内核 global void Kernel int N int a if threadIdx x
  • 使用 ViewBag 时出现 RuntimeBinderException

    我们收到 Layout cshtml 中使用的 Viewbag 项目的 RuntimeBinderException 我们在内存分析器中观察到这些异常 它们不是致命的 一切正常 但很烦人 我们想清除它们 例如 以下代码会导致异常 Rende
  • 在一个整数中找到另一个整数的 MSB 位置左侧的 N 个连续零位

    问题是 给定一个整数val1然后 给定第二个整数 找到最高位组 最高有效位 的位置val2找到第一个整数生成的位置左侧的未设置位的连续区域 width指定minimum必须在连续中找到的未设置位的数量 即width里面没有 0 这是我的解决
  • 编译器如何解析在变长数组之后声明的变量的地址?

    假设我有以下函数 它使用可变长度数组 void func int size int var1 int arr size int var2 编译器如何确定地址var2 我能想到的唯一方法就是放置arr after var1 and var2
  • 重载解析:这如何不含糊不清?

    假设我们有这段代码 是从一个单独的问题复制的 namespace x void f class C void f using x f f lt 名字f在指定的行上明确指的是x f 至少根据 gcc 和 clang 为什么是x f优先于x C
  • 如何在 asp.net 中用空字符串替换字符串中的任何“/ \\ [ ] : | < > + = ; , ? *”字符

    我想用 asp net c 中的空字符串替换字符串中出现的任何以下字符 我正在尝试将其替换为 mystring contains mystring Replace 目前我正在按照上面的方法进行 有没有更干净的方法来做到这一点 感谢致敬 有很
  • 为什么IL代码中stloc.0后面有一个ldloc.0?

    我正在尝试通过编写小代码片段和检查编译的程序集来学习 CIL 所以我写了这个简单的 if 语句 public static void Main string args Int32 i Int32 Parse Console ReadLine
  • 使用 winforms 、 mdi 、父子窗体,在父窗体下指定空间打开子窗体

    我有一个 winform MAINFORM 需要以此形式打开子窗体 如图所示 黑色部分是一个面板并且包含一个编号 具有多个节点的 LinkLabels 和 Treeview 在其余部分中 我想在单击面板上的链接标签时显示子表单 子表单应完全
  • EF,Code First - 如何在插入时设置自定义 Guid 标识值

    在处理在数据库中插入新实体时 我面临以下问题Guid作为主键 代码优先的 EF 5方法 我知道有很多类似的主题 因为我为此问题运行了几个小时 但我找不到与此问题相关的主题 举个例子 我的 POCO 类是 public class Entit
  • 来自 StreamReader 的原始文件字节,幻数检测

    我试图区分 文本文件 和 二进制 文件 因为我实际上想忽略具有 不可读 内容的文件 我有一个文件 我认为它是 GZIP 存档 我试图通过检测幻数 文件签名来忽略此类文件 如果我在 Notepad 中使用十六进制编辑器插件打开文件 我可以看到

随机推荐

  • C++ 11 std::enable_shared_from_this

    C 11 std enable shared from this 一 介绍 1 申明 std enable shared from this template lt class T gt class enable shared from t
  • JavaScript中的对象解释--访问对象属性、遍历属性for in、检测属性是否存在的方法...

    文章目录 目录 文章目录 1 对象访问属性 2 遍历 枚举 属性for in 3 检测属性是否存在的方法 4 模板字符串 反引号 二 总结 一 对象 1 对象访问属性 1 格式 对象 属性名 或对象 属性名 2 自定义对象 属性名 属性值
  • Ubuntu下使用微信

    介绍 由于微信官网 微信 是一个生活方式 没有linux版本的下载和安装方法 但微信确实提供了优麒麟发行版的官方版本 所以就有了下面的安装方法 安装方法 方法一 打开优麒麟应用商店官网微信微信作为一款国民级APP 已经成为我们日常生活中不可
  • flutter 渐变色

    flutter 颜色渐变 Positioned fill 使用绝对定位可全局渐变 可不用 child Container decoration BoxDecoration gradient LinearGradient 渐变位置 begin
  • 如何快速转载CSDN中的博客

    前言 对于喜欢逛CSDN的人来说 看别人的博客确实能够对自己有不小的提高 有时候看到特别好的博客想转载下载 但是不能一个字一个字的敲了 这时候我们就想快速转载别人的博客 把别人的博客移到自己的空间里面 当然有人会说我们可以收藏博客啊 就不需
  • Hyperledger Fabric 网络搭建详解

    写在前面 博主也是因为一些原因刚刚入坑区块链 我认为在我们入门新技术的时候 入门总是最困难的部分 只要入门了 后面学习起来就会越来越轻松 在网上找了很多文章 我觉得大多条理不是很清楚 本文章会详细介绍fabric v1 0 环境部署 以及在
  • 网络安全(黑客)自学笔记

    前言 作为一个合格的网络安全工程师 应该做到攻守兼备 毕竟知己知彼 才能百战百胜 计算机各领域的知识水平决定你渗透水平的上限 1 比如 你编程水平高 那你在代码审计的时候就会比别人强 写出的漏洞利用工具就会比别人的好用 2 比如 你数据库知
  • 用非阻塞的 socket connect

    呵呵 原来有人碰到跟我一样的问题 引用如下 这是网址 http cache baidu com c m 9f65cb4a8c8507ed4fece763104c8c711923d030678197027fa3c215cc79031c1d3a
  • pycharm中不能使用anaconda中包含的库的解决办法

    参考在pycharm中使用Anaconda之后 自己记录下来 防止以后找不到 1 打开pycharm 2 选择菜单栏中的file 文件 中的settings 设置 找到project后 选中project interpreter 点击右边的
  • Android读取设备内存大小

    获取系统运行内存 RAM 大小 public static String getRAMTotalMemorySize final Context context 获得ActivityManager服务的对象 ActivityManager
  • python新手怎么兼职-自学python可以做什么兼职

    很多朋友都会说 我身边有朋友或者同学是做程序员的 但是他们具体的工作内容 其实很多人是不了解的 这几年随着一些影视作品的出现 里面的主演有的从事开发工程师 大家初步有了一个印象 如果我不想去公司坐班 自己通过这个技能怎么来赚钱 推荐学习 P
  • 解决Unreal Engine 4.7.6的DerivedDataCache在C盘疯狂膨胀的问题

    打开 YourEngineFolder Engine Config BaseEngine ini 将 1 Local Type FileSystem ReadOnly false Clean false Flush false PurgeT
  • cnpm安装步骤

    安装nodeJS 官网下载 http nodejs cn download 选择其他版本下载地址 https nodejs org zh cn download releases 选版本点击下载 然后下载后缀名为msi 因为安装简单 选择好
  • 推荐学习方法——费曼技巧,以教促学,教学相长

    1 说到费曼技巧先来了解一下费曼这个人 费曼 全名理查德 费曼 美国著名物理学家 于1965年获得诺贝尔物理奖 在沉迷于美妙的物理世界的同时 他还热衷于教育事业 喜欢向人们深入浅出的讲解艰深的物理知识 在他的自传里 他提到曾纠结于某篇艰深的
  • java的反射机制

    Java的反射机制 1 定义 指程序可以访问 检测并修改本身的状态或行为的一种能力 并能根据自身行为的状态和结果调整或修改应用所描述行为的状态和相关的语义 简单来说 就是一种能自我修正的机制 2 意义 首先 反射机制极大的提高了程序的灵活性
  • 如何开启VT方法

    第 步 进入BOIS 重启电脑后 屏幕刚亮 笔记本一般快速按F1或F12 台式一般按DEL键 多按几下 成功后 会进入BIOS设置页面 第 步 找到 Intel Virtualization Technology 把 Disabled 修改
  • 跨平台方案Flutter入门——开发环境搭建

    目录 系统配置要求 获取 Flutter SDK 配置环境变量 安装 Android Studio 运行 flutter doctor 1 Anroid 的 SDK 路径 2 许可未同意 Android Studio 安装插件 Androi
  • 快来动手训练属于自己的聊天机器人吧!

    活动时间 北京时间2023年3月16日15 00 17 00 活动形式 在线直播 动手训练营 动手实践 使用 Amazon SageMaker 构建基于开源 GPT J 模型的对话机器人应用 难度 入门 时间 20 分钟 开发者可以使用 A
  • Python 遇到的问题

    目录 问题1 Pycharm 项目中 Cannot find declaration to go to 问题2 TypeError list indices must be integers or slices not tuple
  • 【C++】继承

    需要云服务器等云产品来学习Linux的同学可以移步 gt 腾讯云 lt gt 阿里云 lt gt 华为云 lt 官网 轻量型云服务器低至112元 年 新用户首次下单享超低折扣 目录 一 继承的概念 二 被继承成员访问方式的变化 三 赋值兼容