第四章 多态性

第四章 多态性

4.1 多态性

1.多态性的概念
  • 相同的信息作用于不同对象时引发的不同行为
  • c++的多态性两种主要表现形式:1.调用统一对象的相同函数名可能产生不同的行为,主要通过函数重载实现
    2.不同对象接收到相同信息时产生不同的行为,主要通过虚函数来实现
2.多态性的实现

静态多态性:通过在编译程序时绑定所调用的函数体了来实现,属于静态绑定。
绑定就是把函数名和函数体关联起来的过程
动态多态性 :运行程序时确定绑定对象实现

4.2 虚函数

4.2.1 虚函数的概念 virtual

  • 虚函数只能是成员函数
    使用虚函数可以达到动态调用该函数的目的,实现运行时绑定。虚函数在基类和派生类之间提供了同名接口界面,即相同名字的虚函数可以在派生类中被重新定义,以实现不同的函数功能

  • 虚函数的作用:实现动态联编,程序运行时才绑定即将调用的函数体

  • 必须通过基类的指针或者引用才能实现动态联编

  • 通过基类的指针调用虚函数:1.当指针指向基类对象时,调用的是基类中的虚函数
    2.当指针指向派生类对象时,调用的是派生类中的虚函数

4.2.2 必须使用虚函数的例子

虚函数机制,可以达到以下目的–即根据不同的对象类型实现相应的函数调用
虚函数的实现:在基类的成员函数声明语句加上关键字virtual即可
一旦基类的成员函数被定义成虚函数,该基类的所有派生类的同名成员函数不管声明virtual与否,都是虚函数

class <类名>
{
virtual<返回类型><函数名>(<形参表>);
}

如果通过基类的引用或者指针访问虚函数,程序编译时就不会确定具体的被调用函数,只有在程序运行时才会根据具体的对象类型调用与该对象类型匹配的虚函数,实现了动态绑定
功能:不同的派生类可以根据自己的需要修改从基类继承而来的同名虚函数功能

虚函数的要点:
  1. 虚函数必须时成员函数
  2. 当基类中某个成员函数被定义为虚函数后,该函数在其派生类中就应该重新定义
  3. 在派生类中重新定义虚函数时,其函数名,函数参数个数,函数参数类型及函数参数顺序必须与基类中的完全相同
  4. 虚函数的“虚特性”,必须通过基类的指针或者基类的引用才能完全表现出来,通过对象调用虚函数不能尽享动态绑定
    代码见课本P133

虚函数的覆盖

覆盖(override)指在派生类中通过重新定义在基类中定义的同名函数,将基类中定义的函数功能覆盖掉。
即通过覆盖,派生类对象可以调用派生类自己定义的同名函数功能

  • 覆盖式,通过基类的指针或者引用也可以调用派生类中定义的覆盖函数
  • 隐藏:当基类中的同名函数不是虚函数时,当基类和派生类中的同名函数原型不一致时,派生类和基类的同名函数之间都是隐藏关系而不是覆盖关系
  • 隐藏时,通过基类的指针或者引用不能调用派生类的同名函数

4.2.4 虚函数的传递性

  • 在多重继承中基类派生的所有派生类的原型相同的函数都是虚函数,具有虚函数的一切特性。
    Attention:为了实现多重继承中虚函数的传递性,在派生新类时,需要采用public的继承方式,否则无法实现虚特性

4.2.5 虚析构函数

  • 采用虚析构函数可确保多重继承情况下将对象完全销毁
    在有继承关系时,析构函数就应该设计为虚函数
    如果未将析构函数设为虚函数,可能会导致对象销毁的不完全

4.3 抽象类

4.3.1 纯虚函数

  • 定义:在声明虚函数的同时将其“初始化”为0的函数
  • 纯虚函数一般在基类中没有函数实现
  • 纯虚函数是成员函数,是虚函数的一种特例
  • 基于对现实世界进行抽象的需要:有时设计者需要表达一些抽象的概念或者行为,从而不必要求必须有具体实现,或者该行为根本没有具体的物理意义
  • 作用:在基类中为其派生类保留一个函数名,以便派生类根据需要进行定义
    声明格式
class 类名
{
public:
    virtual 函数返回值类型 函数名(参数表)=0;//声明纯虚函数
}

4.3.2 抽象类

一旦在某类中定义了纯虚函数,则该类只能作为基类用于派生新类,不可用该类创建对象
在其派生类中如果实现了该基类的所有纯虚函数,该派生类则可用于创建对象
含有纯虚函数的类只能用于创建新类,不能用于创建对象,该类被称为抽象类或者抽象基类

抽象类:1.包含至少一个纯虚函数 2.不能用于创建对象 3.只能作为基类用于创建派生类 4.抽象类中的纯虚函数可能是从其父类中继承而来的,因此派生类也可以使抽象类

基本规则

  1. 不能直接用抽象类对象作为函数参数类型,函数返回值类型或者显式转换的类型
  2. 可以使用指向抽象类的指针或引用,以支持运行时多态性

4.3.3 使用抽象类的例子

书P152

4.4 函数重载

同一作用域内让多个不同函数体共用同一个函数名的方式称为函数重载

  • c艹的主要函数重载方式:同一个类的同名成员函数重载,其次是类外的全局函数重载

4.4.1 成员函数重载

函数重载只能靠函数参数的个数或参数类型的不同来加以区分
目的是用相同的函数名表达不同的函数实现,以方便调用者用相同的名字调用不同的函数功能

  • 函数的参数名称是不能用于区分函数的,只有函数的参数类型,参数数量和参数顺序之间的差异才能用于区分函数

4.4.2 全局函数重载(类外的函数重载,即整个作用域范围内的函数重载)

4.4.3 函数的默认参数

  • 函数的默认参数,就是在函数声明时给出了具体值的参数
  • 在调用带有默认参数的函数时,如果调用者不赋值,则系统将自动把默认值赋给参数
    语法格式
返回值类型 函数名(形参1,...,形参n-1=默认值,形参n=默认值)

attention:1. 默认参数的声明是从右往左进行的,即如果某个参数左边的参数有默认值,那那个参数也必须有默认值

4.4.4 二义性问题

4.4.5 虚函数和函数重载的关系

  1. 函数重载虽然函数名相同,但是函数参数不能完全相同;在作用域相同时才能谈函数重载
  2. 虚函数在派生类和基类之间定义(作用域不同),且函数原型要完全相同
  3. 重载函数可以是成员函数或非成员函数,但虚函数只能是成员函数
  4. 重载函数的调用时以所传递参数序列的差别作为调用不同函数的依据;而虚函数时根据指针所指向的对象的不同去调用不同类的虚函数。要实现虚函数的动态绑定还是要使用基类的指针或引用
  5. 重载函数在编译时表现出多态性,是静态绑定;虚函数在运行时表现出多态性,是动态绑定

4.5 运算符重载

4.5.1 运算符重载的意义

运算符重载实际上是函数重载的一种,属于静态多态性
编译器区分运算符重载的依据也是函数的参数个数和类型方面的差异
在c艹中,运算符也算一种函数,只是他的参数有可能不像一般函数那样很明确的书写载函数名后面的括弧中而已
c艹允许程序员根据自己的需要重新定义部分已有的运算符,使其具有一些用户需要的特定功能,这就是所谓的运算符重载

  • 运算符重载将赋予已有运算符新含义,并需要明确该运算符的作用域范围
  • c艹预定义的运算符函数的操作对象只能是基本数据类型
  • 运用运算符重载机制,可以在不增加运算符的情况下,实现用户程序中各种各样的运算和操作

4.5.2 运算符重载的规则

规则如下:
  1. 运算对象中至少含有一个用户自定义的数据类型
  2. 不能违背运算符原来的语法规则,譬如不能改变操作数的个数
  3. 不能改变运算符原先的优先级和结合性等性质
  4. 只能重载C艹已经预定义的运算符,不能自创运算符进行重载
  5. 可对原有运算符函数的功能进行适当改造,但是运算符重载函数的功能应当域预定义的功能相类似
  6. 以下运算符不能重载: sizeof;.;.*;::;?:
  7. C++的以下运算符只能重载为类的非成员运算符:<<(输出运算符),>>(输入运算符)
  8. 绝大多数运算符都可重载

4.5.3 成员和非成员运算符函数重载

运算符重载主要有两种形式:

  • 重载为类的成员函数
  • 重载为类的非成员函数
    运算符不必一定要为成员函数,但是重载为非成员函数是,其操作数至少有一个用户自定义的数据类型
1.成员运算符函数重载

语法格式

<函数返回值类型>operator<运算符>(<形参表>)//重载为成员函数
{
<函数体>;
}
// 其中operator是定义运算符重载函数的关键字,<运算符>是要重载的运算符名称

当运算符重载为类的成员函数时,函数的参数个数比原来参与运算的运算数少1————>解释如下:

  • 在调用重载的运算符函数时,一般需要基于某个对象来调用,这个对象实际上是一个隐含的对象,他就是被调用的运算符函数的第一操作数
  • 系统会自动将运算符函数中隐含的this指针关联到这个隐含的对象,从而实现准确调用

对重载为成员函数的运算符来说,它只能通过成员函数所属类的对象来调用,即被重载的运算符的第一操作数的类型似乎确定的,隐含的,不可改变的———>导致了调用的不方便性

2.非成员运算符函数重载

类的非成员运算符重载函数还可以声明为类的友元函数
语法格式

friend<函数返回值类型>operator<运算符>(<形参表>)//
{
<函数体>;
}

也可以是一般函数
语法格式

<函数返回值类型>operator<运算符>(<形参表>)
{
<函数体>;
}

当运算符函数重载为类的非成员函数时,没有this指针,故第一操作数不可省略,故函数参数个数和原运算数个数相同
对非成员运算符函数的调用不需要限定第一操作数是特定的类类型

4.5.4 单目和双目运算符函数重载

C++有三种运算符:

  • 单目运算符(一元运算符):better重载为成员函数
  • 双目运算符(二元运算符):better重载为成员函数或非成员函数
  • 三目运算符(条件运算符):不能重载
1.单目运算符函数重载(一个操作对象)

可重载为:1.没有参数的成员函数 2.带一个参数的非成员函数
*对于++,–运算符,由于有前缀后缀的差别,因此有固定的语法格式

<函数类型>operator++()          //前缀运算符
<函数类型>operator++(int)       //后缀运算符,int参数仅用于区别
<函数类型>operator--()         //前缀
<函数类型>operator--(int)       //后缀
2.双目运算符函数重载

可重载为:1.带一个参数的成员函数 2.带两个参数的类的非成员函数
两个操作对象:调用该运算符函数的对象本身,由this指针给出

4.5.5 赋值运算符重载荷拷贝构造函数

1.赋值运算符重载

C++两种类型的赋值运算符:复合赋值(+=)和直接赋值(=)

复合赋值运算符重载

op= 操作过程

  1. 将复合赋值运算符右侧表达式值计算出来
  2. 将左边的运算对象的值取出来
  3. 对两个值做“op”运算
  4. 将结果赋给左边的运算对象

重载运算符要按上以上操作设计步骤
复合赋值运算符的左值必须是自定义的类对象变量

直接赋值运算符重载

通过赋值运算符重载可以将右侧对象的数据成员依次复制到左边对象的数据成员中
一般,系统会自动生成一个只能完成“复制赋值”功能的赋值运算符
见书上示意图

拷贝构造函数定义

如果类中没有显式声明拷贝构造函数,编译器会自动生成一个拷贝构造函数来进行对象间的复制

具体见书P174

4.5.6 其他运算符重载

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值