C++ ——继承和多态

C++

  • 知识基础
  • 流操作符
  • 类 & 基础
  • 初始化和重载
  • 类 & 高级
  • 继承和多态
  • 模板 & 标准模板库STL


C++面向对象,我们已经学习了如何通过 类 定义用户自己的一种数据类型及其操作集合;进一步的,如何更加灵活的去使用 类 ?提高我们抽象出来的类的表达能力?——面向对象的特征继承&多态,在现有类的基础上进一步去构建新的类,使得新类特殊化。

一、继承

定义

:在当前类的基础之上构建新的类,新类继承了旧类的全部数据成员和函数成员(除了构造函数和析构函数)。将旧类称为父类(基类),新类称为子类(导出类、派生类)

class <子类名>:<继承修饰符> <父类名>{
	成员 = 父类的成员(-构造/析构) + 子类中定义的特有的成员 +	构造/析构
}

<继承修饰符>:决定了能否在子类中访问父类中的成员!与类定义中的访问修饰符相关。
访问修饰符规定了类外语句能否访问类中的成员;继承修饰符限定了父类成员在子类中的表现。

继承修饰符父类成员在子类中的表现
private父类private -> 子类不允许访问
父类protected -> 子类private
父类public -> 子类private
protected父类private -> 子类不允许访问
父类protected -> 子类protected
父类public -> 子类protected
public父类private -> 子类不允许访问
父类protected -> 子类protected
父类public -> 子类public
  • protcted修饰符,用于继承关系之中,保护父类中的该类成员不被类外语句访问,同时允许子类访问该类成员!
  • 默认的继承修饰符为private
  • 父类的私有成员绝对隐私!!!不被任何除父类自身定义的语句访问!!!
  • 子类是继承了父类私有成员的,只是不允许访问(指子类中定义的特有的函数成员不能使用基类中的私有成员,继承来的父类的函数成员可以正常执行)
  • 显然,父类定义在子类之前,不能调用子类的特有成员;
  • 友元函数不属于成员,不存在继承的问题!

创建对象

构造函数:子类并不会继承父类的构造函数,从定义顺序来看,现有父类后有子类,在创建子类的对象时,会先调用父类的构造函数,然后调用子类的构造函数。

  • 子类特有函数执行之前,来自父类的成员都是已经定义甚至初始化完成的!
  • 如果带参的初始化子类对象,需要向父类传递参数,实现调用正确的父类构造函数初始化父类继承而来的数据成员。
<子类名>::<子类名>(参数列表):<父类名>(参数列表)
例:
Cube::Cube(float wide, float lenght, float high):Rectangle(wide, length)
  • 在定义子类构造函数时,需要指明使用的父类构造函数调用!
  • 首先调用的是Rectangle,使用输入的参数widelength,注意这里的同名!
  • 如果父类没有缺省形式的构造函数,子类必须至少具有一个带参的构造函数像基类构造函数传递参数。

析构函数:先调用子类的析构函数,再调用父类的析构函数。

通过构造函数和析构函数我们定义了一个子类的对象,使用子类对象的方法与正常的类没有任何区别,该对象可以访问的成员包括定义的子类的特有public成员和通过继承得到的父类的public成员!

二、覆盖

父类与子类总是具有相似的逻辑概念,子类区别于父类的是一些特性,可能导致子类可能对相同意义的操作有着微小的差别,这样的问题可以通过覆盖父类中的函数,从而正确实现子类对象逻辑意义上的操作,体现了多态性。

重载同样能够解决功能相似模块的多态,也可以用于子类和父类之间,但是重载是通过形参列表不同由编译器确定调用哪一个函数的!不能解决参数完全一致的情况。

覆盖的特点

  • 只能出现在父类/子类之间;
  • 两个函数形参列表也完全一致;
  • 区分方式:子类对象调用子类定义中的该函数,父类对象调用父类定义中的该函数。
  • 子类新函数成员定义中调用基类中的被覆盖函数,使用作用域辨识符调用,完成子类定义中的任务。

类继承中的覆盖体现了部分多态性,但是覆盖和重载不能体现真正的多态性,只有虚函数才是多态性的表现!

三、虚函数

通过继承得到子类之后,我们可以通过重载和覆盖父类的函数成员得到子类中特有的函数接口,但是,这样的多态是不完善的!
在编译器编译阶段,首先定义父类,对于其所有的成员会进行静态绑定,也就是说,此时父类函数成员互相调用的都是父类中的版本!
接着定义子类,可能会对父类中的函数进行覆盖(重载问题不大),由于静态绑定的机制,子类继承的函数成员间的调用仍然是覆盖之前的版本!
如此我们无法正确的使用子类,一种解决的方式是通过定义子类特有的函数实现父类中成员函数的功能,但是在复杂的类中,函数成员间的相互调用是十分常见的,这种方式不能满足我们多态性的要求!!!
虚函数:在父类的成员函数前添加关键字virtual,要求必须在子类中覆盖它!
在编译阶段,首先定义父类,对于虚函数成员进行动态绑定,也就是说,函数成员间的相互调用版本不确定!
接着定义子类,必须对父类中的虚函数进行覆盖,由于动态绑定机制,子类继承的函数成员间的调用的版本还没有确定!
程序运行中,父类对象调用函数成员并且调用了虚函数成员,链接父类版本;子类对象调用函数成员并且调用到了虚函数成员,链接子类版本。

class D{
public:
	void a(){父类版本};
	void b(){a();}
}
class S:public D{
public:
	void a(){子类版本};//重载父类中的该函数
}
int main(){
	D dad;
	S son;
	D.b();//父类版本!!!动态链接
	S.b();//子类版本
}
  • 覆盖中是使用作用域辨识符,使得子类定义的函数可以使用父类被覆盖函数;
  • 虚函数实现了继承自父类的函数,内部调用使用到了子类覆盖的函数。
    灵活更新了父子类中的成员联系。

四、抽象类

纯虚函数:父类中的函数成员,是一个虚函数,但是没有在父类中给出函数体;

  • 继承父类的子类必须重载纯虚函数;
  • 各个派生类根据需要,分别覆盖它,实现真正意义上的多态性;
  • 格式virtual 函数返回值类型 函数名(形参列表)=0;

如果一个类含有一个或者多个纯虚函数,这个类就是抽象类。

  • 抽象类处于类层次的上层;
  • 不能定义抽象类的对象,只能通过继承派生非抽象子类,定义子类对象;

父类对象指针:(1)指向父类对象的指针可以指向其子类的对象;(2)子类覆盖了父类的成员,通过父类指针调用的仍然是基类成员;(3)如果该成员是虚函数,那么调用的是子类中的虚函数版本。


总结

继承与多态是面向对象的基本特征,通过继承大大方便了我们设计类的难度,继承的侧重点在于在定义派生类的过程中怎样充分利用父类子类之间的联系,设计出能够实现新的需求的类!多态的侧重点是在如何利用抽象的方式,是用少量的接口实现多种功能,对于创建的对象来说,只是简单调用就能实现正确的逻辑功能!

  • 重载:相似逻辑意义上的功能,不同的输入甚至输出,同一范围内的函数使用同一函数名;编译器根据参数选择调用的是哪一个!
  • 覆盖:继承关系之中,逻辑意义相同的功能,输入一致,对于单个成员函数,继承发展父类的函数。编译器根据对象的区别决定成员调用操作符之后使用哪一个类定义中的函数!
  • 虚函数:继承关系之中,函数成员之间的联系(相互调用),由于先后定义顺序和静态绑定机制,父类成员的联系不能在子类中更新;所以引入虚函数动态绑定,编译器不确定如何联系和调用;运行阶段确定!
  • 纯虚函数 & 抽象类:在 类 基础上更高层次的抽象化,甚至不能被实例化,因为具体的操作,没有被实现,只是命名了一些逻辑上操作的名字;更多是逻辑上的支持作用。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值