C++基础(8.继承_多态)

目录

继承:

继承的概念:

继承的定义:

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

继承中的作用域:

派生类的默认成员函数:

例题:

​编辑​编辑​编辑

继承与友元:

 继承与静态成员:

复杂的菱形继承及菱形虚拟继承:

继承与组合:

多态:

1. 多态的概念:

2. 多态的定义及实现:

多态的构成条件 :

虚函数重写的一些其他问题:

override 和 final 关键字:

重载、覆盖(重写)、隐藏(重定义)的对比:

纯虚函数和抽象类:

多态的原理 :


继承:

1.继承的概念:

继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在
持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象
程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,
承是类设计层次的复用
class Person{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18;  // 年龄
};// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
以看到变量的复用。调用Print可以看到成员函数的复用。class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

2.继承的定义:

        定义格式:

Person 是父类,也称作基类。 Student 是子类,也称作派生类。
         继承关系和访问限定符:
继承基类成员访问方式的变化
类成员 / 继承方式
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继承下来的成员都只能在派生类的类里 面使用,实际中扩展维护性不强


 

3.基类和派生类对象赋值转换 :

        过度设计:

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用 。这里有个形象的说法叫切片
或者切割。寓意把派生类中父类那部分切来赋值过去。
class Person
{
protected :string _name; // 姓名string _sex;  // 性别int _age; // 年龄
};class Student : public Person
{
public :int _No ; // 学号
};void Test ()
{Student sobj ;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj = sobj ;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象sobj = pobj;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &sobjStudent* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp;  //这种情况转换时虽然可以,但是会存在越界访问的问题ps2->_No = 10;
}


4.继承中的作用域:

  1. 在继承体系中基类派生类都有独立的作用域
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员


5.派生类的默认成员函数:

6 个默认成员函数, 默认 的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类
中,这几个成员函数是如何生成的呢?
  1. 子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显示调用
  2. 子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化。
  3. 子类的operator=必须要调用父类的operator=完成父类的复制。需要注意的是子类的operator=隐藏了父类的operator=,所以显示调用父类的operator=,需要指定父类作用域,
  4. 子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序。
  5. 子类对象初始化先调用父类构造再调子类构造
  6. 子类对象析构清理先调用子类析构再调父类的析构
  7. 因为多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们多态章节会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destructor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系


6.例题:


7.继承与友元:

友元关系不能继承 ,也就是说基类友元不能访问子类私有和保护成员
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;
}void main()
{Person p;Student s;Display(p, s);
}


8. 继承与静态成员:

基类定义了 static 静态成员,则整个继承体系里面只有一个这样的成员 。无论派生出多少个子
类,都只有一个 static 成员实例
class Person
{
public :Person () {++ _count ;}
protected :string _name ; // 姓名
public :static int _count; // 统计人的个数。
};int Person :: _count = 0;class Student : public Person
{
protected :int _stuNum ; // 学号
};class Graduate : public Student
{
protected :string _seminarCourse ; // 研究科目
};void TestPerson()
{Student s1 ;Student s2 ;Student s3 ;Graduate s4 ;cout <<" 人数 :"<< Person ::_count << endl;Student ::_count = 0;cout <<" 人数 :"<< Person ::_count << endl;
}


9.复杂的菱形继承及菱形虚拟继承:

单继承
一个子类只有一个直接父类时称这个继承关系为单继承
多继承:
一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承
菱形继承是多继承的一种特殊情况。
从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
Assistant的对象中Person成员会有两份。

虚拟继承:
哪个公共基类产生冗余和二应性时,继承使用虚继承。
可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在 Student Teacher 的继承 Person 时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地 方去使用。


10.继承与组合:

  • public继承是一种is-a的关系。也就是说每个子类对象都是一个父类对象。
  • 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
  • 继承允许你根据父类的实现来定义子类的实现。这种通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见。继承一定程度破坏了父类的封装,父类的改变,对子类有很大的影响。子类和父类间的依赖关系很强,耦合度高。
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
  • 优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就用继承,另外要实现多态,也必须要继承。类之间的关系既适合用继承(is-a)也适合组合(has-a),就用组合。
  • 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有一些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之一,后来的一些编程语言都没有多继承,如Java。



多态:

前言:
需要声明的,本节课件中的代码及解释都是在 vs2013 下的 x86 程序中,涉及的指针都是 4bytes
如果要其他平台下,部分代码需要改动。比如:如果是 x64 程序,则需要考虑指针是 8bytes 问题
等等

1. 多态的概念:

多态的概念:通俗来说,就是多种形态, 具体点就是去完成某个行为,当不同的对象去完成时会
产生出不同的状态

多态(polymorphism)的概念:通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运行时多
态(动态多态),这里我们重点讲运行时多态,编译时多态(静态多态)和运行时多态(动态多态)。编译时
多态(静态多态)主要就是我们前面讲的函数重载和函数模板,他们传不同类型的参数就可以调用不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在
编译时完成的,我们把编译时一般归为静态,运行时归为动态。

运行时多态,具体点就是去完成某个行为(函数),可以传不同的对象就会完成不同的行为,就达到多种
形态。比如实票这个行为,当普通人买票时,是全价买票;学生买票时,是优惠买票(5折或75折);军人买票时是优先买票。再比如,同样是动物叫的一个行为(函数),传猫对象过去,就是”(>^ω^<)喵“,传狗对象过去,就是“汪汪”。


2. 多态的定义及实现:

多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如 Student 继承了
Person Person 对象买票全价, Student 对象买票半价。

那么在继承中要 构成多态还有两个条件
  • 必须通过基类的指针或者引用调用虚函数
  • 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

虚函数:
即被 virtual 修饰的类成员函数称为虚函数,非成员函数不能加virtual修饰.

说明:

要实现多态效果,第一必须是基类的指针或引用,因为只有基类的指针或引用才能既指向派生类对象;第二派生类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派生类才能有不同的函数,多态的不同形态效果才能达到。

虚函数的重写/覆盖:

派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称派生类的虚函数重写了基类的虚函数。

注意:

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用不过在考试选择题中,经常会故意埋这个坑,让你判断是否构成多态。

例题:

!!!!!!!不要对继承来的缺省值做变 !!!!!!!!! 

(没记错的话在Effective C++的180左右有讲过)


3.虚函数重写的一些其他问题:

1. 协变(了解):

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。协变的实际意义并不大,所以我们了解一下即可。

2. 析构函数的重写:

基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,所以基类的析构函数加了vialtual修饰,派生类的析构函数就构成重写。

下面的代码我们可以看到,如果~A(),不加virtual,那么delete p2时只调用的A的析构函数,没有调用
B的析构函数,就会导致内存泄漏问题,因为~B()中在释放资源。

class A
{
public:virtual ~A(){cout << "~A()" << endl;}
};class B : public A {
public:// 构成重写~B(){cout << "~B()->delete:" << _p << endl;delete _p;}
protected:int* _p = new int[10];
};int main()
{A* p1 = new A;A* p2 = new B;// p1->destructor() + operator deletedelete p1;delete p2;return 0;
}

注意:

这个问题面试中经常考察,要结合类似上面的样例才能讲清楚,为什么基类中的析构函数建议设计为虚函数。


4.override final 关键字:

从上面可以看出,C++对虚函数重写的要求比较严格,但是有些情况下由于疏忽,比如函数名写错参数写重写错等导致无法构成重写,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此C++11提供了override,可以帮助用户检测是否重写。如果我们不想让派生类重写这个虚函数,那么可以用final去修饰

1.  override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car
{
public:virtual void Drive(){}
}; class Benz :public Car 
{
public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
2. final :修饰虚函数,表示该虚函数不能再被重写
class Car
{
public:virtual void Drive(){}
};class Benz :public Car 
{
public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

5.重载、覆盖(重写)、隐藏(重定义)的对比:


6.纯虚函数和抽象类:

虚函数的后⾯写上 =0 ,则这个函数为纯虚函数 ,纯虚函数不需要定义实现(实现没啥意义因为被
派⽣类重写,但是语法上可以实现),只要声明即可。 包含纯虚函数的类叫做抽象类 ,抽象类不 实例化出对象 ,如果 派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类 。纯虚函数某种程度上强制了 派⽣类重写虚函数,因为不重写实例化不出对象。

7.多态的原理

动态绑定与静态绑定:

虚函数表

 

  • 基类对象的虚函数表中存放基类所有虚函数的地址。
  • 派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表 指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基 类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴的。
  • 派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的函数地址。
  • 派⽣类的虚函数表中包含,基类的虚函数地址,派⽣类重写的虚函数地址,派⽣类⾃⼰的虚函数地 址三个部分。
  • 虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后⾯放了⼀个0x0000000标 记。(这个C++并没有进⾏规定,各个编译器⾃⾏定义的,vs系列编译器会再后⾯放个0x00000000 标记,g++系列编译不会放)
  • 虚函数存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址⼜存到了虚表中。
  • 虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,我们写下⾯的代码以对⽐验证⼀下。vs下是存在代码段(常量区)


8.同类型对象虚表共用,不同类型对象虚表各自独立:

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

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

相关文章

【C++】C++入门概念(二)

引用 概念 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用的变量共用同一块内存空间。 比如&#xff1a;李逵&#xff0c;在家称为"铁牛"&#xff0c;江湖上人称"黑旋…

汇川AM600 与 codesys opc server 通讯并建立 OPCserver 的方法

AM600 与 codesys opc server 通讯并建立 OPCserver 的方法 AM600 OPC Server 架构 1、程序后台“应用中”添加 symbols config 2、 选择作为 OPCsever 跟 PLC 通讯数据 点击 ACCERSS rights 下的箭头选择权限 安装目录下:打开“OPCConfig”

re题(38)BUUCTF-[FlareOn6]Overlong

BUUCTF在线评测 (buuoj.cn) 运行一下.exe文件 查壳是32位的文件&#xff0c;放到ida反汇编 对unk_402008前28位进行一个操作&#xff0c;我们看到运行.exe文件的窗口正好是28个字符&#xff0c;而unk_402008中不止28个数据&#xff0c;所以猜测MessageBoxA&#xff08;&#x…

MySQL --表的约束

文章目录 1.空属性2.默认值3.列描述4.zerofill5.主键6.自增长7.唯一键8.外键9.综合案例-阅读 1.空属性 两个值&#xff1a;null&#xff08;默认的&#xff09;和not null(不为空) 数据库默认字段基本都是字段为空&#xff0c;但是实际开发时&#xff0c;尽可能保证字段不为空…

AI客服对决:智能客服PK赛揭示企业级AI应用未来

如今的AI对抗场景&#xff0c;简直成了颇具娱乐性的“观赏项目”。围观群众们不仅看得津津有味&#xff0c;时不时还会发出阵阵笑声。 最近&#xff0c;一场别开生面的AI客服挑战赛引发了热议&#xff1a;一位使用“花开富贵”阿姨人设的Agent甩出一条链接&#xff0c;结果对面…

如何在命令执行超时时自动终止该命令

问题 问题 《Command line command to auto-kill a command after a certain amount of time》 中的回答提出了一种从 bash 命令行中为长时间运行的命令设置超时的方法&#xff1a; ( /path/to/slow command with options ) & sleep 5 ; kill $!但是有可能某个“耗时长”…

python绘制弦图-科研作图

一、背景 弦图以其直观、精美的展示方式受到越来越多人的关注&#xff0c;它不仅能够有效展示两个变量之间的联系&#xff0c;还能同时展现多个变量间的复杂互动&#xff0c;本文将通过Python语言中的pycirclize库&#xff0c;带你深入了解如何绘制弦图。 弦图是一种圆…

CQRS模型解析

简介 CQRS中文意思为命令于查询职责分离&#xff0c;我们可以将其了解成读写分离的思想。分为两个部分 业务侧和数据侧&#xff0c;业务侧主要执行的就是数据的写操作&#xff0c;而数据侧主要执行的就是数据的读操作。当然两侧的数据库可以是不同的。目前最为常用的CQRS思想方…

强大的重命名工具 | Bulk Rename Utility v4.0 便携版

软件简介 Bulk Rename Utility是一款功能强大且易于使用的文件批量重命名工具。它不仅体积小巧&#xff0c;而且完全免费&#xff0c;提供了友好的用户界面。该软件允许用户对文件或文件夹进行批量重命名&#xff0c;支持递归操作&#xff0c;即包含子文件夹的重命名。 软件特…

镜像导入、标签设置与应用、探针测试

一、镜像导入 1、master主机下载镜像并打包 [rootk8s-master ~]# docker pull nginx:1.20.0[rootk8s-master ~]# docker pull nginx:1.21.0[rootk8s-master ~]# docker pull nginx:1.25.0[rootk8s-master ~]# docker pull busybox:latest[rootk8s-master ~]# docker save -o n…

FreeRTOS下UART的封装

FreeRTOS下UART的封装_哔哩哔哩_bilibili Git使用的一个BUG&#xff1a; 当出现这个问题是因为git本身的安全证书路径有问题&#xff0c;我们需要重新指定路径 P1:UART程序层次

蓝桥杯嵌入式的学习总结

一. 前言 嵌入式竞赛实训平台(CT117E-M4) 是北京国信长天科技有限公司设计&#xff0c;生产的一款 “ 蓝桥杯全国软件与信息技术专业人才大赛-嵌入式设计与开发科目 “ 专用竞赛平台&#xff0c;平台以STM32G431RBT6为主控芯片&#xff0c;预留扩展板接口&#xff0c;可为用户提…

正点原子RK3588(二)——lenet测试和modelzoo模型

文章目录 一、lenet二、modelzoo模型2.1 介绍2.2 测试 一、lenet import cv2 import numpy as np from rknnlite.api import RKNNLite RKNN_MODEL LeNet5_mnist_model.rknndef show_top5(result):output result[0].reshape(-1)output_sorted sorted(output, reverseTrue)to…

AI资深导师指导-ChatGPT深度科研工作应用、论文撰写、数据分析及机器学习与AI绘图

2022年11月30日&#xff0c;可能将成为一个改变人类历史的日子——美国人工智能开发机构OpenAI推出了聊天机器人ChatGPT3.5&#xff0c;将人工智能的发展推向了一个新的高度。2023年4月&#xff0c;更强版本的ChatGPT4.0上线&#xff0c;文本、语音、图像等多模态交互方式使其在…

Fyne ( go跨平台GUI )中文文档-绘图和动画(三)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI…

GAMES101(15节,辐射,BRDF)

Irradiance辐射度量学 辐射度量学在渲染领域&#xff0c;可以帮助理解基于物理的光照模型 radiant energy辐射能量Q&#xff0c;累计总能量&#xff08;单位J joule焦耳&#xff09;&#xff0c;就像太阳能板&#xff0c;光照时间越长接收能量越多&#xff0c;收到的能量总和…

python是什么语言写的

Python是一种计算机程序设计语言。是一种面向对象的动态类型语言。现今Python语言很火&#xff0c;可有人提问&#xff0c;这么火的语言它的底层又是什么语言编写的呢&#xff1f; python是C语言编写的&#xff0c;它有很多包也是用C语言写的。 所以说&#xff0c;C语言还是很…

解密的军事卫星图像在各种民用地理空间研究中都有应用

一、美军光学侦察卫星计划概述 国家侦察局 &#xff08;NRO&#xff09; 负责开发和操作太空侦察系统&#xff0c;并为美国国家安全开展情报相关活动。NRO 开发了几代机密锁眼 &#xff08;KH&#xff09; 军事光学侦察卫星&#xff0c;这些卫星一直是美国国防部 &#xff08;D…

Upload-Labs 文件上传靶场 通关

Pass-01 (本pass在客户端使用js对不合法图片进行检查&#xff01;) Pass-02 Pass-02 (本pass在服务端对数据包的MIME进行检查!) Pass-03 Pass-03 (本pass禁止上传.asp|.aspx|.php|.jsp后缀文件&#xff01;) AddType application/x-httpd-php .php .phtml .php3 .php4 .p…

Java-Part 0

Advanced Java and Cutting-edge Applications Part 0: Course presentation Part 1 其实就是个括号匹配问题&#xff0c;Stack 经典问题&#xff0c;但是好久没用Java&#xff0c;有一点点生疏&#xff0c;感觉老师的版本要简洁的多 package tiei.ajp.test;import java.uti…