目录
1.继承
1.1什么是继承?
1.2语法格式
1.3继承权限
1.4继承概念语法格式
1.5赋值兼容规则
1.6继承体系中的作用域
1.7在继承体系中的构造和析构
1.8静态成员继承
1.9友元的继承
1.10不同继承方式下子类的对象模型
1.11继承和组合
2.多态
2.1什么是多态
2.2多态的分类
2.3实现条件
2.4重写
2.5抽象类
2.6C++实现多态的原理
2.7多态面试题
1.继承
1.1什么是继承?
1.2语法格式
class Student :public Person{
public:
int _stuid;
int _major;
};
1.3继承权限
1.4继承概念语法格式
好像没有这个东西
1.5赋值兼容规则
前提:必须是public的继承方式。因为:public继承下,子类和基类是is-a的关系,即:可以将子类对象看成是一个基类对象
1.5.1基类和派生类的赋值转换
class A {
protected:
int a;
}
class B : public A {
protected:
int b;
}
1.6继承体系中的作用域
1.6.1基类和子类隶属于不同的作用域
1.6.2同名隐藏||重定义
基类和子类具有相同名称的成员变量,与成员变量的类型是否相同无关
基类和子类具有相同名称的成员函数,与成员函数的类型是否相同无关
通过子类对象调用同名成员时,只能访问到子类自己的同名成员,基类的无法直接访问到,如果想要通过子类对象访问基类中同名的成员,必须加:基类名称::同名成员
1.7在继承体系中的构造和析构
1.7.1子类的构造方法怎么写,子类对象构造的过程
1.父类没有声明构造函数:
子类也没有声明自己的构造函数,则父类和子类均由编译器生成默认的构造函数。
子类中声明了构造函数(无参或者带参),则子类的构造函数可以写成任何形式,不用顾忌父类的构造函数。在创建子类对象时,先调用父类默认的构造函数(编译器自动生成),再调用子类的构造函数。
2.父类只声明了无参构造函数
如果子类的构造函数没有显式地调用父类的构造,则将会调用父类的无参构造函数。也就是说,父类的无参构造函数将会被隐式地调用。
3.父类只声明了带参构造函数
因为父类只有带参的构造函数,所以如果子类中的构造函数没有显示地调用父类的带参构造函数,则会报错,所以必需显示地调用。
4.父类同时声明了无参和带参构造函数
子类只需要实现父类的一个构造函数即可,不管是无参的还是带参的构造函数。如果子类的构造函数没有显示地调用父类的构造函数(无参或带参),则默认调用父类的无参构造函数。
总结一下:
1.当父类有显式地声明了构造函数时,子类最低限度的实现父类中的一个
2.当父类没有声明构造函数时,子类可以不声明构造函数或者任意地书写构造函数
1.7.2 子类对象析构的过程
编译器在编译器阶段,会在子类析构函数最后一条语句之后添加调用基类析构函数的指令
1.7.3 派生类六大默认成员函数
1.8静态成员继承
在整个继承体系中,静态成员只有一份
可以看做子类共享父类的静态成员的内存,子类可以修改该内存中的值,因此父类调用时也被修改了。 所以并不能看成继承关系,可以认为是共享关系。
在子类重新定义一个相同的静态成员变量,子类和父类的静态成员变量其实是两个不同的东西了(开辟了两块静态变量空间),修改子类并不会修改父类中的值。
子类可以继承、覆盖父类的静态成员函数
1.9友元的继承
友元关系不能继承的,因为友元不是类的成员
1.10不同继承方式下子类的对象模型
1.10.1对象模型是什么?
对象模型是指在面向对象编程中,为了描述和处理现实世界中的实体、事物或概念所构建的抽象模型。它通常由类、对象、属性、方法等元素组成,用于描述实体之间的关系、结构和行为。
1.10.2不同继承方式
1.单继承
一个子类只有一个直接父类时称这个继承关系为单继承,基类部分在上,子类部分在下
2.多继承
3.菱形继承:单继承加多继承
3.1语法格式
class Person
{
public :
string _name ; // 姓名
};
class Student : public Person
{
protected :
int _num ; //学号
};
class Teacher : public Person
{
protected :
int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
3.2存在的问题
二义性:基类中的一个属性被两个派生类继承,最终子类继承的时候会从这两个不同的派生类得到两个属性,造成歧义。
3.3解决方案
1.让访问明确化:在成员之前添加类名:: 2.菱形虚拟继承,利用虚继承,关键字virtual。
4.虚拟继承
4.1怎么实现虚拟继承?
在继承权限前virtual
4.2与普通继承方式的区别?
1.虚继承的原理
虚继承用于解决多继承条件下的菱形继承问题(数据冗余、存在二义性)。
底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(也被称作虚基表,不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
实际上,vbptr 指的是虚基类表指针(virtual base table pointer,也叫虚基表指针),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。说人话就是:BC的指针和A在同一块内存中,BC的偏移量不同,通过偏移量找到对应的A
图解;
2.虚基类的声明和语法形式
class 派生类名:virtual 继承方式 基类名
3.虚基类的注意事项
一个类可以在一个类族中用作虚基类,也可以用作非虚基类。
在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的对象。
虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化 (最派生类会先去调用虚基类的构造函数)。
最派生类是指在继承类结构中建立对象时所指定的类。
在派生类的构造函数的成员初始化列表中,必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数。
在虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中,都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
在一个成员初始化列表中,同时出现对虚基类和非虚基类构造函数的调用时,基类的构造函数先于非虚基类的构造函数执行。
虚基类并不是在声明基类时声明的,而是在声明派生类是,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。
4.虚基表指针的存放位置
虚基表指针是存放在数据段的
虚基表指针是放在对象的开头的
5.补充说明
编译器会生成默认的成员函数----给对象前4个字节中填充虚基表的地址
基类成员是通过从虚基表中获取偏移量来访问的
5.菱形虚拟继承
上面子类对象模型,包括虚基表中的内容
1.11继承和组合
2.多态
2.1什么是多态?
2.2多态的分类
1.静态多态||早绑定||静态绑定||静态联编
程序在编译时可以确定函数的具体行为,即:具体调用那个函数(函数重载,模板)
2.动态多态||晚绑定||动态绑定||动态联编
在程序运行时,根据基类的指针或者引用引用的具体的类对象,选择对应的虚函数进行调用,因为编译阶段,编译器无法知道基类的指针或者引用到底指向那个类的对象
2.3实现条件
1.必须在继承的体系下
2.子类必须对基类中的虚函数进行重写
3.虚函数调用:必须通过该基类的指针或者引用调用虚函数
4.多态体现:当程序运行时,根据基类的指针或者引用指向不同的类的对象,选择合适的虚函数进行调用
2.4重写
2.4.1什么是重写?
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
1.基类的成员函数必须是虚函数
2.子类虚函数原型必须要和基类的虚函数原型一直,实现不同
2.4.2特殊情况
1.协变
2.析构函数
2.4.3 final 和 override
2.4.4函数重载 || 重定义 || 重写区别
2.5抽象类
2.5.1什么是抽象类
文章来源地址https://www.toymoban.com/news/detail-498367.html
2.5.2特性
不能实例化对象
子类必须对抽象类中所有的纯虚函数进行重写,否则子类也是抽象类
2.5.3接口继承和实现继承
2.6C++实现多态的原理
2.6.1对象模型
类中包含有虚函数,编译器会给该类的对象多增加4个字节(32位系统),目的:存储虚函数表(虚表)的地址
2.6.2虚表中虚函数的存放方式
1.基类:按照虚函数在类中声明的先后次序依次添加到虚表中,本质:实际就是函数指针数组
2.子类虚表构造:
I:将基类虚表中内容拷贝一份放到子类的虚表中
II: 如果子类重写了某个基类虚函数,则使用子类虚函数的地址替换虚表中相同偏移量位置的基类虚函数地址
III: 子类新增加的虚函数按照其在虚表中声明的先后次序依次添加到虚表的最后
3.注意事项
同一个类的多个对象共享同一份虚表,即不同对象前4个字节中存放的虚表的地址是一样的
基类和子类不会共享虚表,虚表各自是各自的,即使虚表中内容完全相同
虚表是在编译时候生成好的
虚表和虚函数都是存放在代码段的 虚表存的是虚函数指针 对象中存的也不是虚表而是虚表指针
多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
虚表实际上是在构造函数初始化列表阶段进行初始化的
拷贝构造出来的父类对象p1和p2当中的虚表指针指向的都是父类对象的虚表。因为同类型的对象共享一张虚表,他们的虚表指针指向的虚表是一样的。
2.6.3虚函数调用原理
必须满足多态的条件,则多态按照一下方式处理:
1. 从基类的指针或者引用:指向的实际对象的前4个字节中获取虚表的地址
2. 传递该函数的参数 以及 this指针
3. 从虚表中找到对应的虚函数进行调用
构成多态,指向谁就调用谁的虚函数,跟对象有关。
不构成多态,对象类型是什么就调用谁的虚函数,跟类型有关。
如果出现菱形继承
文章来源:https://www.toymoban.com/news/detail-498367.html
2.7多态面试题
到了这里,关于C++ 第三弹继承和多态-类和对象的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!