C++程序设计教程PPT-C++多态与虚函数PPT

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

· 虚函数实现多态
· 虚函数实现多态地原理· 虚析构函数· 纯虚函数
· 抽象类
C++中地多态分为静态多态与动态多态。

静态多态是函数重载,在编译阶段就能确定调用哪个函数。

动态多态是由继承产生地,指同一个属性或行为在基类和其各派生类中具有不同地语义,不同地对象根据所接收地消息作出不同地响应,这种现象称为动态多态。

例如,动物都能发出叫声,但不同
地动物叫声不同,猫会“喵喵”,
狗会“汪汪”,这就是多态地体现。

面向对象程序设计中所说地多态通常指地是动态多态。

在C++中,“消息”就是对类地成员函数地调用,不同地行为就是函数地实现不同,因此,多态地本质是函数地多种形态。

多态地实现需求满足3个条件:(1)基类声明虚函数。

(2)派生类重写基类地虚函数。

(3)将基类指针指向派生类对象,通过基类指针访问虚函数。

虚函数地声明方式是在成员函数地返回值类型前添加virtual关键字,格式如下所示:
class 类名
{
权限控制符:
virtual 函数返回值类型 函数名(参数列表);
… //其它成员
};
声明虚函数时,有以下3点需求注意:
(1)构造函数不能声明为虚函数,但析构函数可以声明为虚函数。

(2)虚函数不能是静态成员函数。

(3)友元函数不能声明为虚函数,但虚函数可以作为另一个类地友元函数。

虚函数只能是类地成员函数,不能将类外地普通函数声明为虚函数
若类中声明了虚函数,并且派生类重新定义了虚函数,当使用基类指针或基类引用操作派生类对象调用函数时,系统会自动调用派生类中地虚函数代替基类虚函数。

若类中声明了虚函数,并且派生类重新定义了虚函数,当使用基类指针或基类引用操作派生类对象调用函数时,系统会自动调用派生类中地虚函数代替基类虚函数。

//例5-1 polymorphic.cpp
class Animal{//动物类Animal
public:
virtual void speak();//声明虚函数speak()
};
void Animal::speak(){//类外实现虚函数
cout<<"动物叫声"<<endl;
}
class Cat :public Animal{//猫类Cat公有继承Animal类public:
virtual void speak();//声明虚函数speak()
};
void Cat::speak(){//类外实现虚函数
cout<<"猫地叫声:喵喵"<<endl;
}
class Dog:public Animal{//狗类Dog公有继承Animal类public:
virtual void speak();//声明虚函数speak()
};
void Dog::speak(){//类外实现虚函数
cout<<"狗地叫声:汪汪"<<endl;
}
int main()
{
Cat cat;//创建Cat类对象cat
Animal *pA=&cat;//定义Animal类指针pA指向对象cat
pA->speak();//通过pA调用speak()函数
Dog dog;//创建Dog类对象dog
Animal *pB=&dog;//定义Animal类指针pB指向对象dog
pB->speak();//通过pB调用speak()函数
return 0;
}
上述代码定义了三个类:
动物类Animal:该类定义了speak()函数,表示动物地叫声。

猫类Cat:公有继承Animal类,Cat类重写了speak()函数。

狗类Dog:公有继承Animal类,Dog类重写了speak()函数。

在main()函数中创建Cat类对象cat,定义Animal类指针pA指向对象cat,然
后通过pA调用speak()函数,则调用地是Cat地speak()函数。

创建Dog类对象dog,定义Animal类指针pB指向对象dog,然后通过pB调用speak ()函数,调用地是Dog地speak()函数。

派生类对基类虚函数覆写时,必须与注 意
基类中虚函数地原型完全一致,派生
类中覆写地虚函数前是否添加
virtual,均被视为虚函数。

1.override
override关键字地作用是检查派生类中函数是否在重写基类虚函数,如果不是重写地基类虚函数,编译器就会报错。

class Base{//基类Base
public:
virtual void func();
void show();
};
class Derive :public Base{//派生类Derive公有继承Base类public:
void func() override;//可通过编译
void show() override;//不能通过编译
};
利用override关键字可以判断派生类是否准确地对基类虚函数进行重写,防止出现因书写错误而导致地基类虚函数重写失败。

另外,在实际开发中,C++中地虚函数大多跨层继承,直接基类没有声明虚函数,但很可能会从“祖先”基类间接继承。

如果类地继承层次较多或者类地定义比较复杂,那么在定义派生类时就会出现信息分散,难以阅读地问题,重写基类虚函数时,往往难以确定重写是否正确。

此时,可以通过override关键字进行检查。

2.final
final关键字有两种用法:修饰类,修饰虚函数。

当使用final关键字修饰类时,表示该类不可以被继承。

class Base final//final修饰类
{
public:
//...
};
class Derive :public Base//Base类不能被继承{
public:
//...
};
当使用final关键字修饰虚函数时,虚函数不能在派生类中重写。

class Base
{
public:
virtual void func() final;
};
class Derive :public Base
{
public:
void func();//不能通过编译
};
函数调用时,需求根据函数名,函数返回值类型,函数参数信息正确调用函数,这个匹配过程通常称为绑定。

C++提供了两种函数绑定机制:静态绑定与动态绑定。

静态绑定也称为静态联编,早绑定,它是指编译器在编译时期就能确定要调用地函数。

动态绑定也称为动态联编,迟绑定,它是指编译器在运行时期才能确定要调用地函数。

虚函数就是通过动态绑定实现多态地,当编译器在编译过程中遇到virtual 关键字时,它不会对函数调用进行绑定,而是为包含虚函数地类建立一张虚函数表Vtable。

在虚函数表中,编译器按照虚函数地声明顺序依次保存虚函数地址。

同时,编译器会在类中添加一个隐藏地虚函数指针VPTR,指向虚函数表。

在创建对象时,将虚函数指针VPTR放置在对象地起始位置,为其分配空间,并调用构造函数将其初始化为虚函数表地址。

需求注意地是,虚函数表不占用对象空间。

派生类继承基类时,也继承了基类地虚函数指针。

当创建派生类对象时,派生类对象中地虚函数指针指向自己地虚函数表。

在派生类地虚函数表中,派生类虚函数会覆盖基类地同名虚函数。

当通过基类指针或基类引用操作派生类对象时,以操作地对象内存为准,从对象中获取虚函数指针,通过虚函数指针找到虚函数表,调用对应地虚函数。

class Base1{//定义基类Base1 public:
virtual void func();//声明虚函数func()
virtual void base1();//声明虚函数base1()
virtual void show1();//声明虚函数show1() };
class Base2{//定义基类Base2 public:
virtual void func();//声明虚函数func()
virtual void base2();//声明虚函数base2()
virtual void show2();//声明虚函数show2() };
//定义Derive类公有继承Base1与Base2
class Derive :public Base1, public Base2
{
public:
virtual void func();//声明虚函数func()
virtual void base1();//声明虚函数base1()
virtual void show2();//声明虚函数show2() };
在上述代码中,基类Base1有三个虚函数:func(),bas ()与show1();基类Base2有三个虚函:func(),base2 ()与show2();派生类Derive公有继承Base1与Base2,Derive类声明了虚函数func(),base1
()与show2()。

Derive类与Base1类与Base2类地继承关系
函数表。

Derive类继承自Base1类与Base2类,也会继承两个基类地虚函数指针。

Derive类地虚函数func(),base1()与show2()会覆盖基类中地同名虚函数。

如果创建Derive类对象(如derive),则对象derive地内存逻辑示意图如下图。

5.2.2 虚函数实现多态地机制
通过基类Base1,基类Base2地指针或
引用操作Derive类对象,在程序运行
时,编译器从Derive类对象内存中获
取虚函数指针,通过指针找到虚函数表,
调用相应地虚函数。

不同地类,函数实
现都不一样,在调用时就实现了多态。

在C++中不能声明虚构造函数,因为构造函数执行时,对象还没有创建,不能按照虚函数方式调用。

但是,C++可以声明虚析构函数,虚析构函数地声明是在“~”符号前添加virtual关键字,其格式如下所示:
virtual ~析构函数();
在基类中声明了虚析构函数之后,基类地所有派生类地析构函数都自动成为虚析构函数。

引用操作派生类对象,在析构派生类对象时,
编译器会先调用派生类地析构函数释放派生
类对象资源,然后再调用基类析构函数。

如果
基类没有声明虚析构函数,在析构派生类对象
时,编译器只会调用基数地析构函数,不会调
用派生类析构函数,导致派生类对象申请地资
源不能正确释放。

//例5-2 vdestructor.cpp
class Base{//基类Base
public:
virtual ~Base();//虚析构函数
};
Base::~Base(){
cout<<"Base类析构函数"<<endl;
}
class Derive :public Base{//派生类Derive公有继承Base类public:
~Derive();//虚析构函数
};
Derive::~Derive()
{
cout<<"Derive类析构函数"<<endl;
}
int main()
{
Base *pb=new Derive;//基类指针指向派生类对象
delete pb;//释放指针
return 0;
}
上述代码中,基类Base中定义了虚析构函数,则派生类Derive中地析构函数也自动为虚析构函数。

在main()函数中,定义Base 类指针pb指向一个Derive类对象,然后使用delete运算符释放pb指向地空间,程序会先调用派生类地析构函数,再调用基类地虚析构函数。

有时候在基类中声明函数并不是基类本身地需
求,而是考虑到派生类地需求,在基类中声明一个函数,函数地具体实现由派生类根据本类地需求定义。

例如,动物都有叫声,但不同地动物叫声不同,因此基类(动物类)并不需求实现描述动物叫声地函数,只需求声明即可,函数地具体实现在各派生类中完成。

在基类中,这样函数可以声明为纯虚函数。

纯虚函数也通过virtual关键字声明,但是纯虚函数没有函数体。

纯虚函数在声明时,需求在后面加上“=0”,其格式如下所示:virtual 函数返回值类型 函数名(参数列表) = 0;
上述格式中,纯虚函数后面“=0”并不是函数地返回值为0,
它只是告诉编译器这是一个纯虚函数,在派生类中会完成具体地实现。

纯虚函数地作用是在基类中为派生类保留一个函数接口,方便派生类根据需求完成定义,实现多态。

派生类都应该实现基类地纯虚函数,如果派生类没有实现基类地纯虚函数,则该函数在派生类中仍然是纯虚函数。

如果一个类中包含纯虚函数,这样地类称为抽象类。

抽象类地作用主要是通过它为一个类群建立一个公共接口(纯虚函数),使它们可以更有效地发挥多态性。

抽象类声明了公共接口,而接口地完整实现,由派生类定义。

抽象类只能作为基类派生新类,不能创建抽象
类地对象,但可以定义抽象类地指针或引用,通过指针或引用操作派生类对象。

抽象类可以有多个纯虚函数,如果派生类需求实例化对象,在派生类中需求全部实现基类地纯虚函数,如果派生类没有全部实现基类地纯虚函数,未实现地纯虚函数在派生类中仍然是纯虚函数,则派生类也是抽象类。

//例5-3 abstract.cpp
class Animal{//动物类Animal public:
virtual void speak()=0;//纯虚函数
virtual void eat()=0;//纯虚函数
virtual ~Animal();//析构函数};
Animal::~Animal()
{
cout<<"调用Animal析构函数"<<endl;
}
class Cat :public Animal{//猫类Cat公有继承Animal类public:
void speak();//声明speak()函数
void eat();//声明speak()函数
~Cat();//声明析构函数};
void Cat::speak(){ cout<<"小猫喵喵叫"<<endl; } void Cat::eat(){ cout<<"小猫吃鱼"<<endl; } Cat::~Cat(){ cout<<"调用Cat析构函数"<<endl; }
class Rabbit :public Animal{//兔子类Rabbit公有继承Animal类public:
void speak();//声明speak()函数
void eat();//声明eat()函数
~Rabbit();//声明析构函数
};
void Rabbit::speak(){ cout<<"小兔子咕咕叫"<<endl; }
void Rabbit::eat(){ cout<<"小兔子吃白菜"<<endl; } Rabbit::~Rabbit(){ cout<<"调用Rabbit析构函数"<<endl; }
int main(){
Animal* pC=new Cat;//定义基类指针pC指向Cat类对象
pC->speak();//通过pC指针调用speak()函数
pC->eat();//通过pC指针调用eat()函数
delete pC;//释放指针pC指向地空间
Animal *pR=new Rabbit;//定义基类指针pR指向Rabbit类对象
pR->speak();//通过pR指针调用speak()函数
pR->eat();//通过pR指针调用eat()函数
delete pR;//释放pR指向地空间
return 0;
}
上述代码定义了3个类:
动物类Animal:该类提供了两个纯虚函数:speak()与eat()。

猫类Cat:公有继承Animal类,Cat类实现了Animal类地全部纯虚函数。

兔子类Rabbit:公有继承Animal类,Rabbit类实现了Animal类地全部纯函数。

在main()函数中,分别使用Animal类地指针指向Cat类与Rabbit类地对象,通过Animal类指针成功调用了Cat类与Rabbit类地函数,实现了多态,并且成功调用了Cat类与Rabbit类地析构函数,释放了各自对象。

一.案例描述
停车场管理系统是模拟停车场进行车辆管理地系统,该系统分为汽车信息,普通用户与管理员用户三个模块。

3汽车信息模块普通用户模块管理员用户模块
二.案例分析
通过停车场管理系统案例描述可知,停车场
管理系统需求展现一个菜单界面,用户可以根据选项进入普通用户与管理用户界面,普通用户只能查看车辆信息,只有管理用户才能添加,修改,删除车辆地信息。

普通用户与管理用户具备返回主菜单地功能。

下面根据模块地功能,为每个模块设计类。

1.汽车信息管理类设计
根据案例描述,针对汽车信息模块可以设计一个类Car,汽车属性设计为其成员变量,汽车信息模块地功能设计为其成员函数。

点击查看详细设计。

相关文档
最新文档