概念:
(1)继承是从已有的类创建新类的过程,这使得创建和维护一个应用程序变得更容易,达到了重用代码功能和提高执行时间的效率
(2)继承呈现了面向对象程序设计的层次结构,体现了从简单到复杂的认知过程
(3)在c++类中,如果类b继承于类a,则类a叫做基类或者父类,则b叫做派生类或者子类
语法
class 派生类名:继承方式1<基类名1>,继承方式2<基类名2>...
(语法格式:比较类似函数名后初始化的方式)
继承类型:
当一个类派生自基类,该基类可以被继承为public,protected或者private几种类型。
当使用不同类型的继承时,遵循以下几个规则:(逐渐降级)
(1)公有继承public:当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生内的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。(一般使用这种方式最多)
(2)保护继承protected:当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员
(3)私有继承private:当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员
无法被继承的成员
(1)基类的构造函数,拷贝构造函数和析构函数
(2)基类的重载运算符
(3)基类的友元函数与友元类
继承之后的成员函数的隐藏
(1)父类与子类有同名函数,子类对象调用的时候总是调用子类的函数,此时父类成员函数被隐藏(限制作用域又可以重新调回父类函数?)
(2)一般子类继承过来的函数不适合子类,或者需要扩展,则需要重新设计继承而来的父类函数
(3)函数返回值与参数可相同可不同,只需要函数名保持一致(只有virtual中考虑函数的返回值影响,其他暂时没看到函数返回值影响的内容)
(4)父类的成员函数
(5)子类的成员函数
构造函数调用顺序
构造一个子类时:(调用顺序跟函数初始化值的顺序无关)
(1)先调用父类构造函数----只跟继承填写的前后顺序有关
(2)再调用成员对象构造函数----只跟声明顺序有关
(3)最后调用自身构造函数
(记忆口诀:先父母,后兄弟,再自己)
析构函数调用顺序---与构造函数相反
继承--组合
概念:通过对现有对象进行拼接即组合,产生新的具有更复杂的功能
和继承的区别:
(1)若在逻辑关系上B是A的一种,则允许B继承A的功能。一个类可以从另一个类中派生,是另外一种类的一种
(2)若在逻辑上A是B的一部分,则不允许B继承A的功能,而要用A和其他东西组合出B,由多种类组合而成,不是派生而成
继承与多重继承
多继承:一个派生类有多个基类
多重继承:一个派生类作为其他派生类的基类
多重继承菱形问题:
(对于此类情况也有对应的处理方式:virtual虚继承,后面此项技术需要额外补充学习)
数据冗余问题:
当D类继承于B类与C类,B类与C类有一个共同的基类时,在创建D类的对象时,A类的构造函数将会调用两次,相当于创建两个A类对象
二义性问题:
A类中成员变量,可以通过B和C去访问,此时回存在两个同种含义的变量
解决方案===使用虚继承
虚继承和动态多态的实现
多态
多态的表现
(1)现实生活中需要有相同类型,不同的情况进行对应处理。
例如:不同类型的人买票有不同的售价
(2)对于同一个行为对于不同的对象,有不同的表现,是面向对象编程三大特性之一(封装,继承,多态)
多态在c++中分为静态多态与动态多态(目前学习的技术是动态多态virtual虚函数)
(1)静态多态是基于函数重载与泛型编程实现的
(2)动态多态基于虚函数实现的(需要遵循以下规则)
必须通过基类对象的指针或引用调用函数
被调用的函数必须是虚函数,且派生类需要重写基类的虚函数
(为什么需要基类对象的指针或者引用调用函数呢。)
c++中,一般针对一个行为只会有一个名称,是对类的行为在做一个抽象
主要作用在于统一行为的接口,提高方法的通用性
静态绑定与动态绑定
区别:
(1)静态绑定指程序编译结束后就已经确定了需要调用的函数
(2)动态绑定是指在运行时才确定具体需要调用的函数
(一个是gcc编译阶段,一个是./a.out运行阶段,具体不是很理解,暂时没有具体实现过)
作用:
(1)把不同的派生类对象都当作基类对象来看,可以屏蔽不同子类对象之间的差异
(2)提高程序的通用性来适应需求的不断变化
动态多态的实现过程
实现:
(1)创建两个类,并且是继承关系
(2)基类中的函数声明称virtual函数,也就是虚函数
(3)派生类继承基类并且重写基类中的虚函数
(4)通过基类的指针或者引用访问派生类的对象
(virtual基类中填写了,子类中可以省略)
重点:重写的特点
(1)拥有不同的作用域(分别位于派生类与基类)
(2)函数名相同,参数相同,返回值类型相同
(3)基类必须有virtual关键字,不能有static
(4)重写函数的权限访问限定符可以不同(即可以public也可以protected也可以private)
特别的---存在以下两种例外
(1)析构函数:如果不构成多态,那么指针什么类型就会调用什么类型的析构函数,这样也就会导致:如果派生类的析构函数中有资源要释放,而这里却没有释放掉那些资源,就会导致内存泄漏的问题(即析构函数可以重写:虽然函数名不同)
(2)协变:重写的虚函数,返回值可以不同,但是返回值必须是父,子类的指针或引用类型,即为协变(返回值不同的情况:返回值必须是父,子类的指针或引用类型,如果存在协变的情况也可以重写)
说明符final与override
final:指定某个虚函数不能再子类中被重写,或者某个类不能被子类继承(跟函数的const关键字一样 写在最后)
在虚函数上使用:
在类上使用:
override关键字---用来检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错(用来检查派生类虚函数是否重写正确)也和final关键字一样写在最后
几个概念的区别:重写,重载,隐藏
重写--特点:
(1)不同的作用域(分别位于派生类与基类)
(2)函数名相同,参数相同,返回值相同
(3)基类必须要有virtual关键字,不能用static
(4)重写函数的权限访问限定符可以不同
成员函数隐藏--特点
(1)基类与派生类有同名函数,调用的时候总是调用子类的函数,此时父类成员函数被隐藏
(2)在不同的作用域内(分别位于派生类与基类)
(3)函数名相同,返回值可相同可不同
(4)参数不同时,不论有无virtual关键字,基类函数将被隐藏(注意与重载的区别:重载发生在同一作用域内)
(5)参数相同时,但基类没有virtual关键字,基类的函数被隐藏(注意与覆盖的区别:覆盖是必须要有virtual关键字)
函数重载---特点
(1)同一片作用域内
(2)函数名相同
(3)返回值可以相同可以不相同
(4)参数列表不同
重写与隐藏的区别
子类对象:子类对象在调用的时候,总是调用子类实现的成员函数
父类对象:
(1)重写:用父类的指针或引用指向子类对象的时候,覆盖的情况下,父类指针或者引用调用的函数是子类实现的函数
(2)隐藏:用父类的指针或者引用指向子类对象的时候,隐藏的情况下,父类指针或者引用的函数是父类实现的函数
多态原理与虚函数表
(和函数隐藏的this参数指针不一样,没有具体的体现形式,但是我们可以通过代码发现和体现出以下具体形式)
虚函数表:实际上对于定义了虚函数的类对象来说,有一个隐藏的虚函数表指针,指向表中存放着虚函数的地址
多态原理:由于动态绑定:因此当父类的指针或者引用去调用虚函数时,会到运行时具体的对象的虚函数表彰进行寻找对应的虚函数进行调用,若为父类类型,则调用父类虚函数,若为派生类类型,则调用派生类虚函数
代码验证实现:
父类:
子类
地址打印
(1)B类存在堆区
(2)B存在栈区
抽象类与纯虚函数
相关定义:含纯虚函数的类,成为抽象类
纯虚函数---指定函数接口规范,而不做的实现,实现部分由继承它的子类去实现