C++---继承

继承

  • 前言
  • 继承的概念及定义
    • 继承的概念
    • 继承定义
      • 继承关系和访问限定符
  • 基类和派生类对象赋值转换
  • 继承中的作用域
  • 派生类的默认成员函数
  • 继承与友元
  • 继承与静态成员
  • **多重继承**
    • 多继承下的类作用域
    • 菱形继承
    • 虚继承
      • 使用虚基类
    • 支持向基类的常规类型转换

前言

在需要写Father类和Mother类的时候,需要给这两个类写一些属性,像 名字,性别,年龄,爱好,电话,家庭地址等,这两个类中会有一些共同的属性,把这些公共的属性进行提取,封装成一个Person类,Father和Mother继承Person,就不需要在写共同的属性了。这就是本章要说的继承

继承的概念及定义

继承的概念

继承机制是面向对象程序设计是代码可以复用的最重要的手段,它允许程序猿在保持原有类特性的基础上进行扩展,增加功能,这样产生的新的类,称为派生类。继承呈现了面向对象程序设计的层次结构。层次结构的根部有一个基类,派生类就是直接或间接的从基类继承而来。基类负责定义在层次结构中所有类的共同拥有的成员,而每个派生类定义各自特有的成员。

class Person
{
public:void Print(){cout << "name>>" << _name << endl;cout << "age>>" << _age << endl;}string _name = "A";int _age = 18;
};class Student : public Person
{
protected:int _stuid = 123;
};int main()
{Student s;s.Print();return 0;
}

在这里插入图片描述

通过调试可以看出,派生类中有基类的成员,也有自己定义的特殊的成员。

继承定义

	   派生类   继承方式  基类
class Student : public Person

继承关系和访问限定符

在这里插入图片描述


继承基类的时候,访问限定符的不同,成员访问也会变化。

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类的不可见在派生类中不可见
  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。。

  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private。

  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强 。

基类和派生类对象赋值转换

通常情况下,如果我们想把引用活指针绑定到一个对象上,则引用或指针的类型应与对象的类型一致,或者对象的类型含有一个可接受的const类型转换规则。存在继承关系的类是也给重要的列外:我们可以将基类的指针或引用绑定到派生类对象上。 见上面代码将Student对象的地址赋值给Person*

在这里插入图片描述

之所以存在派生类向基类的类型转换是因为每个派生类对象都包含了一个基类的部分,而基类的引用或者指针可以绑定到该基类的部分上。

Student s;
Person* p1 = &s;
Person& p2 = s;

p1的过程中派生类会将父类的那一部分切出来拷贝过去,p2的过程则是子类中父类的部分的别名。只有派生类对象中的基类部分会被拷贝,移动或者赋值,它的派生类部分将被忽略掉。

	s.Print();p1->_name = "B";p1->Print();p2._name = "C";p2.Print();

在这里插入图片描述

编译器在编译时无法确定某个特定的转换在运行时是否安全,这是因为编译器只能通过检查指针或者引用的静态类型来推断该转换是否合法。如果在基类中含有一个或多个虚函数,我们可以使用 dynamic_cast请求一个类型转换,该转换的安全检查将在运行时执行。

  • 从派生类向基类的类型转换只对指针或者引用类型有效
  • 基类向派生类不存在隐式类型转换
  • 和任何其他成员一样,派生类向基类的类型转换也可能会由于访问受限而变得不可行

自动类型转换只对指针或者引用有效,但是继承体系中的大多数类仍然(显示或隐式的)定义了拷贝控制成员。因此,我们通常能够将一个派生类对象拷贝,移动,或者赋值给一个基类对象。不过这种操作只处理派生类中的基类部分。

继承中的作用域

每个类定义自己的作用域,这个作用域内我们定义的成员,当存在继承关系时,派生类的作用域嵌套,在其基类的作用域之内。如果一个名字在派生类的作用域内无法正确解析时,编译器将继续在外层的基类作用域中寻找改名字的定义。


和其他作用域一样,派生类也能重用定义在其直接基类或简介基类中的名字,此时定义在内层作用域(既派生类)的名字将隐藏定义在外层作用域(既基类)的名字。

class Person
{
public:void Print(){cout << "name>>" << _name << endl;cout << "age>>" << _age << endl;}string _name = "A";int _age = 18;
};class Student : public Person
{
public:void Print(){cout << "age>>" << _age << endl;cout << "Person.age>>" << Person::_age << endl;}protected:int _stuid = 123;int _age = 20;
};int main()
{Student s;s.Print();return 0;
}

输出结果是 20 18

派生类的成员将隐藏同名的基类成员。
如果想要访问基类中的成员,可以使用作用域运算符来使用隐藏的成员


class Person
{
public:void Print(){cout << "Person::Print()" << endl;}string _name = "A";int _age = 18;
};class Student : public Person
{
public:void Print(int i){cout << "Student::Print()" << endl;}
protected:int _stuid = 123;int _age = 20;
};

代码中的两个Print是什么关系呢?

隐藏

为什么不是重载呢?同名函数,参数不同,构成重载很合理。

但是重载有一个限定,同一个作用域。

派生类的默认成员函数

class Person
{
public:Person(const char* name = "A"):_name(name){}protected:string _name;int _age = 18;
};class Student : public Person
{
public:Student(const char* name = "B"):_id(0),_name(name){}
protected:int _id;
};

在父类中写了构造函数后,想在子类中给继承而来的name成员进行初始化,但是Student中的构造函数给name成员初始化的操作却报错了。

因为派生类的构造只能构造在派生类中新增的成员,要想调用基类的构造函数可以在派生类的初始化列表中加上 Person(name)

	Student(const char* name = "B"):_id(0),Person(name)

如果不写 Person(name)的话,派生类在执行构造函数的时候,会自动的去调用基类的构造函数(要有默认参数,不然会报错).


	Student(const Student& s):Person(s) // 将基类 = 派生类,会有切片操作,将属于基类的部分进行切割。,_id(s._id){}

拷贝构造中也要调用父类Person才能完成。

如果在拷贝构造中不写Person(s),就会去调用默认拷贝构造函数。


	Student& operator= (const Student& t){cout << "Student& operator= (const Student& t)" << endl;if (this != &t){Person::operator=(t);// 不写Person:: 会造成隐藏_id = t._id;}return *this;}
	Person& operator= (const Person& n){cout << "Person& operator= (const Person& n)" << endl;if (this != &n){_name = n._name;}return *this;}

在基类和派生类中分别写一个赋值运算符,派生类也需要调用父类的赋值运算符。


在析构函数体执行完后,对象的成员会被隐式销毁,类似的,对象的基类部分也是隐式销毁的。因此,和构造函数及赋值运算符不同的是,派生类析构函数只负责销毁由派生类自己分配的资源。

class Person
{
public:Person(const char* name = "A"):_name(name){cout << "Person" << endl;}Person& operator= (const Person& n){cout << "Person& operator= (const Person& n)" << endl;if (this != &n){_name = n._name;}return *this;}~Person(){cout << "~Person" << endl;}protected:string _name;
};class Student : public Person
{
public:Student(const char* name = "B"):_id(0),Person(name){cout << "Student(const char* name = 'B')" << endl;}Student& operator= (const Student& t){cout << "Student& operator= (const Student& t)" << endl;if (this != &t){Person::operator=(t);_id = t._id;}return *this;}~Student(){Person::~Person();cout << "~Student" << endl;}Student(const Student& s):Person(s),_id(s._id){}protected:int _id;
};int main()
{Student s("张三");return 0;
}

后面由于多态的原因,析构函数的函数名被特殊处理了,统一处理成destructor。

在执行代码后

在这里插入图片描述

先给基类初始化,然后派生类初始化,在调用析构发现,基类的析构多调用了一次。

对象销毁的顺序正好与创建的顺序相反,创建对象的时候是先调用父类的构造函数,这里子类析构函数首先执行,然后是父类的析构函数,以此类推,沿着继承体系的反方向直至最后。

继承与友元

就像友元关系不能传递一样,有缘关系同样也不能继承,基类的友元在访问派生类成员时不具有特殊属性,类似的,派生类的友元也不能随意访问基类的成员。

class Student;
class  Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name = "S"
};class Student : public Person
{
protected:int _id = 1;
};void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._id << endl;
}int main()
{Person p;Student s;Display(p, s);return 0;
}

不能继承友元关系,每个类负责控制各自成员的访问权限。

继承与静态成员

如果基类定义了一个静态成员,则在整个继承体系只存在该成员的唯一定义。不管有多少个派生类,静态成员只有一个实例。

class Student;
class  Person
{
public:string _name = "S";static int num;
};int Person::num = 0;class Student : public Person
{
protected:int _id = 1;
};int main()
{Person p;Student s;cout << &s.num << endl;cout << &p.num << endl;return 0;
}

输出结果会发现,这两个地址是一样的。

静态成员遵循通用的访问控制规则。

多重继承

一个子类只有一个直接父类时称这个继承为单继承。

在这里插入图片描述

多重继承是指一个子类有两个或以上直接父类,多继承的派生类继承了所有父类的属性。从概念上看不是很难,但是多个基类相互交织就会产生一些特殊的问题。

在这里插入图片描述

构造一个派生类的对象将同时构造并初始化它的所有基类子对象。与从一个基类进行的派生一样,多继承的派生类的构造函数初始值也只能初始化它的直接基类。

多继承下的类作用域

在只有一个基类的情况下,派生类的作用域嵌套在直接基类和间接基类的作用域中。查找过程沿着继承体系自底向上进行,直到找到所需要的名字,派生类的名字将隐藏基类的同名成员。

在多重继承的情况下,相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到了,则对该名字的使用将具有二义性。

菱形继承

在这里插入图片描述

一个学生,它继承了A,也继承了B,A和B都继承了Person,前面也说过,派生类会继承基类的所有属性,如果A中存在age,B中也存在age,在Student中访问age就会导致二义性(不知道访问哪个),无法明确知道访问的是哪一个。

class Person
{
public:string _name;
};class A : public Person
{
public:string _id;
};class B : public Person
{
public:string _num;
};class Student : public A, public B
{
public:string _sex;
};int main()
{Student s;s._name = "张三";return 0;
}

在这里插入图片描述

当然,可以通过指定访问哪个父类的成员可以解决二义性的问题,但是数据冗余(浪费空间)的问题无法解决。

	Student s;s.B::_name = "张三";s.A::_name = "李四";cout << s.B::_name << endl;cout << s.A::_name << endl;

在这里插入图片描述

当一个类拥有多个基类,有可能出现派生类从两个或更多基类中继承同名成员的情况,此时,不加前缀限定符直接使用该名字会引发二义性。

虚继承

在默认情况下,派生类中含有继承链上的每个类对应的子部分。如果某个类在派生过程中出现了多次,则派生类中将包含该类的多个子对象。也就是上面的菱形继承造成的二义性和数据冗余问题。

C++给出了虚继承的机制来解决这两个问题。虚继承的目的是令某个类做出声明,承诺愿意共享它的基类。其中,共享的基类子对象称为虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含唯一一个共享的虚基类子对象。

class Person
{
public:string _name;
};class A : virtual public Person
{
public:string _id;
};class B :virtual public Person
{
public:string _num;
};class Student : public A, public B
{
public:string _sex;
};int main()
{Student s;s._name = "张三";cout << s._name << endl;return 0;
}

在这里插入图片描述


#include <iostream>using namespace std;class A
{
public:int _a;
};class B : public A
{
public:int _b;
};class C : public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

在这里插入图片描述

在调试的时候,查看内存,可以发现,这些数据都是挨着放的。

在这里插入图片描述

这是通过前缀限定符解决二义性问题的,可以看出,数据冗余问题还存在。


下面看通过虚继承的方式。

在代码最后添上了 d._a = 0;

在这里插入图片描述

在这里插入图片描述

这样,就没有数据冗余和二义性了。但是BC类中多了两个东西。

在这里插入图片描述

把这两个地址查看一下发现,指向的都是0,但是这个地址的下一个位置存的都有数据。其实通过计算可以发现,1到6的偏移量刚好是20,3到6的偏移量刚好是12.所以BC中多出来的地址存的是距离A的偏移量(相对距离)。

存找基类偏移量的表叫虚机表

为什么要把偏移量给存下来呢?上面说的切片操作,基类以引用或者指针的方式,把派生类=基类。

使用虚基类

我们指定虚基类的方式是添加关键字 virtual

class B :virtual public Person

通过上面的代码,我们将Person 定义为 B的虚基类。

vitrual说明符表示一种愿望,即在后续的派生类当中共享虚基类的同一份实例。

支持向基类的常规类型转换

不论基类是不是虚基类,派生类对象都能被可访问基类的指针或引用操作。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python爬虫实战案例——第五例

文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff01;严禁将文中内容用于任何商业与非法用途&#xff0c;由此产生的一切后果与作者无关。若有侵权&#xff0c;请联系删除。 目标&#xff1a;采集三国杀官网的精美壁纸 地址&#xff1a;aHR0cHM6Ly93d3…

Qt/C++音视频开发54-视频监控控件的极致设计

一、前言 跌跌撞撞摸爬滚打一步步迭代完善到今天&#xff0c;这个视频监控控件的设计&#xff0c;在现阶段水平上个人认为是做的最棒的&#xff08;稍微自恋一下&#xff09;&#xff0c;理论上来说应该可以用5年不用推翻重写&#xff0c;推翻重写当然也是程序员爱干的事情&am…

Visual Studio2019报错

1- Visual Studio2019报错 错误 MSB8036 找不到 Windows SDK 版本 10.0.19041.0的解决方法 小伙伴们在更新到Visual Studio2019后编译项目时可能遇到过这个错误&#xff1a;“ 错误 MSB8036 找不到 Windows SDK 版本 10.0.19041.0的解决方法”&#xff0c;但是我们明明安装了该…

Linux多线程【线程控制】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 文章目录 &#x1f307;前言&#x1f3d9;️正文1、线程知识补充1.2、线程私有资源1.3、线程共享资源1.4、原生线程库 2、线程…

安卓机型固件系统分区的基础组成 手机启动规律初步常识 各分区的基本含义与说明

此贴为基本常识。感兴趣的友友可以了解手机的启动顺序和各模式的基本操作与意义。另外了解手机系统分区各文件夹的含义 分区说明对应贴&#xff1a;安卓机型固件中分区对应说明 手机开机基本启动顺序 当我们按下手机开机键的时候。基本的启动顺序为 注意&#xff1a;该结构图…

Learn Prompt-“标准“提示

在前面的教程中&#xff0c;我们介绍了指令输入的简单提示&#xff0c;提供实例的提示和角色扮演类的提示&#xff0c;那么是否有一个公式来列出提示的各个部分&#xff0c;并将其组合成一个标准化的提示&#xff1f;答案是肯定的。 角色扮演&#xff08;Role&#xff09; 指令…

SQL 性能优化总结

文章目录 一、性能优化策略二、索引创建规则三、查询优化总结 一、性能优化策略 1. SQL 语句中 IN 包含的值不应过多 MySQL 将 IN中的常量全部存储在一个排好序的数组里面&#xff0c;但是如果数值较多&#xff0c;产生的消耗也是比较大的。所以对于连续的数值&#xff0c;能用…

如何用在线模版快速制作活动海报?

在时代的发展和信息传播的快速发展下&#xff0c;活动海报成为了宣传活动的重要方式之一。设计一张吸引眼球的活动海报&#xff0c;不仅能够有效传递信息&#xff0c;还能够吸引人们的注意力。那么&#xff0c;在这里我将教会大家如何设计活动海报&#xff0c;只需要三分钟&…

12.(Python数模)(相关性分析一)相关系数矩阵

相关系数矩阵 相关系数矩阵是用于衡量多个变量之间关系强度和方向的统计工具。它是一个对称矩阵&#xff0c;其中每个元素表示对应变量之间的相关系数。 要计算相关系数矩阵&#xff0c;首先需要计算每对变量之间的相关系数。常用的相关系数包括皮尔逊相关系数和斯皮尔曼相关…

【JAVA-Day14】深入了解 Java 中的 while 循环语句

深入了解 Java 中的 while 循环语句 深入了解 Java 中的 while 循环语句摘要引言一、什么是 while 循环语句二、while 循环语句的语法和使用场景使用场景 三、while 循环的优势和使用场景优势使用建议 四、总结参考资料 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的…

Mysql详解Explain索引优化最佳实践

目录 1 Explain工具介绍2 explain 两个变种3 explain中的列3.1 id列3.2 select_type列3.3 table列3.4. type列3.5 possible_keys列3.6 key列3.7 key_len列3.8 ref列3.9 rows列3.10 Extra列 4 索引最佳实践4.1.全值匹配4.2.最左前缀法则4.3.不在索引列上做任何操作&#xff08;计…

stringBuffer.append(analyze);使用这个拼接时候如何在字符串参数字符串参数整数参数字符串数组参数内容之间添加空格

stringBuffer.append(analyze);使用这个拼接时候如何在字符串参数字符串参数整数参数字符串数组参数内容之间添加空格&#xff1f; 在添加参数到 StringBuffer 时&#xff0c;你可以在每次添加参数之后都添加一个空格&#xff0c;如下所示&#xff1a; StringBuffer stringBu…

零信任:基于Apisix构建认证网关

最终效果 基于身份认证的零信任网关 - 知乎 背景 零信任一直是我们未来主攻的一个方向&#xff0c;全球加速&#xff0c;SD-WAN组网都是一些非常成熟的产品&#xff0c;全球加速是我们所有产品的底座&#xff0c;SD-WAN解决的是多个网络打通的问题&#xff0c;而零信任则主打…

『PyQt5-Qt Designer篇』| 09 Qt Designer中分割线和间隔如何使用?

09 Qt Designer中分割线和间隔如何使用? 1 间隔1.1 水平间隔1.2 垂直间隔2 分割线2.1 水平线2.2 垂直线3 保存并执行1 间隔 间隔有水平间隔和垂直间隔: 1.1 水平间隔 拖动4个按钮,并设置为水平布局: 在第一个按钮的右边添加一个水平间隔: 设置其sizeType为Fixed,宽度为20…

c++ 函数的参数是否可以为auto

&#xff08;1&#xff09;在vs2019开到 cpp20 的语法规范&#xff0c;是可以的 &#xff08;2&#xff09;但网上和文心一言和书上说不可以 (2) 再附上一种auto 的很炫酷的写法&#xff1a;

HTML+CSS画一个卡通中秋月饼

HTMLCSS画一个卡通中秋月饼&#x1f96e;&#x1f96e;&#x1f96e; 中秋活动水个文章 整个divcss实现个月饼&#xff0c;给前端初学者一个练手的demo 效果图 思路 HTMl 先来个轮廓画脸上的东西&#xff1a;眼睛、眉毛、腮红、嘴巴眼睛丰富下瞳孔画20个花瓣 CSS 轮廓是要外…

【MySQL】 MySQL数据库基础

文章目录 &#x1f431;‍&#x1f453;数据库的操作&#x1f4cc;显示当前的数据库&#x1f4cc;创建数据库&#x1f388;语法&#xff1a;&#x1f388;语法说明&#x1f388;示例&#xff1a; &#x1f334;使用数据库&#x1f38b;删除数据库&#x1f431;‍&#x1f3cd;语…

react的状态管理简单钩子方法

1.recoil useProvider文件: import { atom, useRecoilState } from recoil;const initState atom({key: initState,default: {state: [],}, })// 将业务逻辑拆分到一个单独文件中&#xff0c;方便进行状态管理 export interface StateProps {id: number;text: string;isFini…

HTML导航栏二级菜单(垂直、水平方向)

二级菜单是指主菜单的子菜单。菜单栏实际是一种树型结构&#xff0c;子菜单是菜单栏的一个分支。简单分享主要的垂直和水平方向的CSS设计。 垂直方向&#xff1a; HTML: <body><div><ul><li><a href"#">家用电器</a><ul>…

灰狼算法Grey Wolf Optimizer跑23个经典测试函数|含源码

智能优化算法&#xff08;Grey Wolf Optimizer&#xff09; 文章目录 智能优化算法&#xff08;Grey Wolf Optimizer&#xff09;前言一、灵感二、GWO数学模型1、包围猎物2、狩猎3、攻击猎物4、开发5、代码实现 总结 前言 灰狼算法简介&#xff1a; 灰狼优化算法&#xff08;G…