C++ 多态性

在这里插入图片描述

一 多态性的分类

编译时的多态
函数重载
运算符重载
运行时的多态
虚函数

1 运算符重载的引入

使用C++编写程序时,我们不仅要使用基本数据类型,还要设计新的数据类型-------类类型。

一般情况下,基本数据类型的运算都是运算符来表达,这很直观,语义也简单。

例如:

int a,b,c;a=b+c;

对于基本数据类型,就隐含着运算符重载的概念。

在这里插入图片描述
在这里插入图片描述
如果直接将运算符作用在类类型之上,情况又如何呢?
例如:

Complex ret,c1,c2;ret=c1+c2;

编译器将不能识别运算符的语义。
需要一种机制来重新定义运算符作用在类类型上的含义。
这种机制就是运算符重载。

二 两种重载函数的比较

多数情况下,运算符可以重载为类的成员函数,也可以重载为友元函数。但两种重载也有各自特点:
一般情况下,单目运算符重载为类的成员函数;双目元素重载为类的友元函数。
有些双目运算符不能重载为类的友元函数:=,(),[],->
类型转换函数只能定义为类的成员函数,而不能定义为友元函数。
若一个运算符的操作需要修改对象的状态,则重载为成员函数比较好;
若运算符所需要的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选择友元函数;
若运算符是成员函数,最左边的操作数必须是运算符类的对象(或者类对象的引用)。如果左边操作数必须是一个不同类的对象,或者是基本数据类型,则必须重载为友元函数;
当需要重载运算符的元素具有交换性时,重载为友元函数;

1 重载运算符的几点注意事项

大多数预定义的运算符可以被重载,重载后的优先级、结合级及所需的操作数都不变。
但少数的C++运算符不能重载:
例如:::、#、?:、.、
不能重载非运算符的符号,例如:;
C++ 不运行重载不存在的运算符,如"?"、“**”等。

当运算符被重载时,它是被绑定在一个特定的类类型之上的。当此运算符不作用在特定类类型上时,它将保持原有的含义。

当重载运算符时,不能创造新的运算符符号,例如不能用"**"来表示求幕运算符。

应当尽可能保持重载运算符原有的语义。试想,如果在某个程序中用"+“表示减,”*"表示除,那么这个程序读起来将会非常别扭。

三 多态性的引入

1 虚函数和多态性

重载普通的成员函数的两种方式:
在同一个类中重载:重载函数是以参数特征区分的。
派生类重载基类的成员函数:
由于重载函数处在不同的类中,因此它们的原型可以完全相同。调用时使用“类名::函数名”的方式加以区分。
以上两种重载的匹配都是在编译的时候静态完成的。

重载是一种简单形式的多态。
C++提供另一种更加灵活的多态机制:虚函数。虚函数运行函数调用与函数体的匹配在运行时才确定。
虚函数提供的是一种动态绑定的机制。

2 赋值兼容规则

在公有派生方式下,派生类对象可以作为基类对象来使用,具体方式如下:
在这里插入图片描述

派生类拥有从基类继承过来的成员;
基类对象和派生类对象的内存布局方式;
在这里插入图片描述
当一个派生类对象直接赋值给基类对象时,不是所有的数据都赋给了基类对象,赋予的只是派生类对象的一部分。这部分叫做派生类对象的“切片(sliced)”。

注意
回忆一下不同的继承方式,子类对基类中成员的访问权限:
在这里插入图片描述
只有在公有派生的情况下,才有可能出现“基类的公有成员变成派生类的公有成员”的情况。
在这里插入图片描述
通过基类引用或指针所能看到的是一个基类对象,派生类中的成员对于基类引用或指针来说是“不可见的”。

我们能不能“通过基类引用或指针来访问派生类的成员”呢?
为了达到上述目的,我们可以利用C++的虚函数机制,将基类的Print说明为虚函数形式。这样就可以通过基类引用或指针来访问派生类中的Print。

3 虚函数

在基类中用virtual关键字声明的成员函数即为虚函数。

虚函数可以在一个或多个派生类中被重写定义,但要求重定义时虚函数的原型(包括返回值类型、函数名、参数列表)必须完全相同。

3 基类中的函数具有虚特性的条件

在基类中用virtual将函数说明为虚函数。
在公有派生类中原型一致地重载该虚函数。
定义基类引用或指针,使其引用或指向派生类对象。当通过该引用或指针调用需要函数时,该函数将体现出虚特性来。

C++中,基类必须指出希望派生类重定义哪些函数。定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

注意:
在派生类中重载虚函数时必须与基类中的函数原型相同,否则该函数将丢失虚特性。

仅返回类型不同,其他相同。C++编译器认为这种情况是不允许的。

函数原型不同,,仅函数名相同。C++编译器认为这是一般的函数重载,此时虚特性丢失。

四 虚函数与多态性

1 提供虚函数的意义

提升软件的重用性
基类使用虚函数提供一个接口,但派生类可以定义自己的实现版本。
虚函数调用的解释依赖于它的对象类型,这就实现了“一个接口,多种语义”的概念。
提供软件架构的合理性。

2 虚函数和虚指针

在编译时,为每个有虚函数的类建立一张虚函数表VTABLE,表中存放的时每一个虚函数的指针;同时用一个虚指针VPTR指向这张表的入口。
访问某个虚函数时,不是直接找到那个函数的地址,而是通过VPTR间接查到它的地址。

在这里插入图片描述
在这里插入图片描述
对象的内存空间除了保存数据成员外,还保存VPTR。VPTR由构造函数来初始化。

3 对虚函数的要求

虚函数必须是类的非静态成员函数。
不能将虚函数说明为全局函数。
不能将虚函数说明为静态成员函数。
不能将虚函数说明为友元函数。

本质的原因就是非静态成员函数隐含传递this指针,而通过this指针能够找到VPTR。

4 在成员函数中调用虚函数

在一个基类或派生类的成员函数中,可以直接调用类等级中的虚函数。此时需要根据成员函数中this指针所指向的对象来判断调用的时哪一个函数。

5 析构函数可以定义为虚函数

构造函数不能定义为虚函数。
而析构函数可以定义为虚函数。

若析构函数为虚函数,那么当使用delete释放基类指针所指向的派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。

五 纯虚函数与抽象类

在这里插入图片描述

基类中的这些公共接口只需要有售卖而不需要有实现,即纯虚函数。纯虚函数刻画了派生类应该遵循的协议,这些协议的具体实现由派生类来决定。

在这里插入图片描述
将一个函数说明为纯虚hasn’t,就要求任何派生类都定义自己的实现。
拥有纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为基类被使用。
抽象类的派生类需要实现纯虚函数,否则该派生类也是一个抽象类。
当抽象类的所有函数成员都是纯虚函数时,这个类被称为接口类。

小结:
继承和动态绑定在两个方面简化了我们的程序:
能够容易地定义与其他类相似但又不相同的新类,能更容易地编写忽略这些相似类型之间区别的程序。

许多应用程序的特性可以用一些相关但略有不同的概率描述。面向对象编程与这种应用非常匹配。通过继承可以定义一些类型,可以模型不同冲类;通过动态绑定可以编写程序,使用这些类而又忽略与具体类型相关的差异。

继承和动态绑定的思想在概念上非常简单,但对于如何创建应用程序以及对于程序设计语言必须支持得特性,含义深远。

面向对象编程的关键思想是多态性。因为在需要情况下可以互换地使用派生类型或基类型的“许多形态”,所以称通过继承而相关联的类型为多态类型。C++中,多态型仅用于通过继承而相关性的类型的引用或指针。

我们称因继承而相关的类构成一个继承层次。其中一个类称为根,所有其他类直接或间接继承根类。

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值