C++多态



多态简介


多态是面向对象的程序设计的关键技术。多态:调用同一个函数名,可以根据需要但实现不同的功能。多态体现在两个方面,我们以前学过的编译时的多态性(函数重载)和现在我们这一章将要学习的运行时的多态性(虚函数)。


运行时多态:运行时的多态性是指在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况来动态地确定。

 

前面我们在介绍继承的时候,通过基类的指针或引用指向派生类对象后,调用派生类和基类中同名的方法的时候,调用的均是基类的成员,不能访问派生类的成员。这是在运行之前就可以确定的,而要实现运行时的多态,我们要通过虚函数来实现。



多态使用


1.若要访问派生类中相同名字的函数,必须将基类中的同名函数定义为虚函数,基类的指针指向派生类对象后, 就可以调用派生类的同名的成员函数。


CCurrentTime currentTime;
CTime *p = ¤tTime;
CTime& time = currentTime;
time.getHour();


在前面我们没有将基类的getHour函数设置为虚函数,所以虽然这是一个包含派生类的对象的基类的引用,它也不能根据我们刚才所说的运行时多态来判断所包含的对象来调用该类的getHour函数,只会根据基类的这是一个基类的引用来调用基类的getHour函数,指针也是一样。

 

1)将基类同名的函数定义为虚函数可以使用C++关键字virtual来实现。在基类成员函数声明前加virtual关键字。


2)在派生类中重新定义基类中的虚函数时,可以不用关键字virtual来修饰这个成员函数 。

 

在程序的执行过程中,依据指针具体指向哪个类对象,或依据引用哪个类对象,才能确定绑定哪个成员函数,实现动态绑定。


注意:


1)当在基类中把成员函数定义为虚函数后,在其派生类中定义的虚函数必须与基类中的虚函数同名,参数的类型、顺序、参数的个数必须一一对应,若函数名相同,但参数的个数不同或者参数的类型不同时,则属于函数的重载,而不是虚函数。若函数名不同,显然这是不同的成员函数。


2)实现这种动态的多态性时,必须使用基类类型的指针变量,并使该指针指向不同的派生类对象,并通过调用指针所指向的虚函数才能实现动态的多态性。通过对象名访问虚函数则不会实现动态多态性。


3)在派生类中没有重新定义虚函数时,与一般的成员函数一样,当调用这种派生类对象的虚函数时,则调用其基类中的虚函数。


4)可把析构函数定义为虚函数,但是,不能将构造函数定义为虚函数。构造函数不能被继承,每一个类都需要自己的构造函数来初始化对象。当派生类的对象销毁的时候,先调用派生类的析构函数,再调用基类的析构函数,如果基类的析构函数不是虚函数,在对基类的指针或者引用进行销毁的时候,调用的就是基类的析构函数,没有调用派生类的析构函数,一般的情况下这是没有问题的。但是我们如果需要在派生类的内部执行一些必要的清理工作,比如释放一些占用的内存,或者释放占用线程的锁,这个时候不调用派生类的析构函数就会导致内存泄漏。我们一般讲基类的析构函数设置为虚函数。


5)多态时通过虚函数动态绑定实现。内部则是通过虚函数表实现。调用时的执行速度要慢。虚函数表是一张表,其内部存储很多虚函数的地址。基类对象有一张,子类也有一张,初始时子类继承基类的表,若子类覆盖(重写)了基类的虚函数,则将子类的虚函数表对应项目替换为子类虚函数指针。


这个大家先大概了解,随着理解的加深,可以深入学习一下。



抽象类


在介绍抽象类之前,我们先介绍一下纯虚函数。


1.纯虚函数


在基类中仅仅给出声明,不对虚函数实现定义,而是在派生类中实现。这个虚函数称为纯虚函数。普通函数如果仅仅给出它的声明而没有实现它的函数体,这是编译不过的。纯虚函数没有函数体。


纯虚函数需要在声明之后加个=0;


class    <基类名>
{
  virtual <类型><函数名>(<参数表>)=0; ......
};


2.抽象类


含有纯虚函数的类被称为抽象类。抽象类只能作为派生类的基类,不能定义对象,但可以定义指针。在派生类实现该纯虚函数后,定义抽象类对象的指针,并指向或引用子类对象。


1)在定义纯虚函数时,不能定义虚函数的实现部分;


2)在没有重新定义这种纯虚函数之前,是不能调用这种函数的。


抽象类的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类中的成员函数的基础,并实现动态多态性。继承于抽象类的派生类如果不能实现基类中所有的纯虚函数,那么这个派生类也就成了抽象类。因为它继承了基类的抽象函数,只要含有纯虚函数的类就是抽象类。纯虚函数已经在抽象类中定义了这个方法的声明,其它类中只能按照这个接口去实现。


3.接口和抽象类的区别


1)C++中我们一般说的接口,表示对外提供的方法,提供给外部调用。是沟通外部跟内部的桥梁。也是以类的形式提供的,但一般该类只具有成员函数,不具有数据成员;


2)抽象类可以既包含数据成员又包含方法。



抽象类的实例


1.抽象类IShape作为基类:只有头文件,没有实现文件


#ifndef SHAPE_H
#define SHAPE_H
#include<string>
using std::string;
//interface
class IShape
{
public:
virtual float getArea()=0;  //纯虚函数,获得面积
virtual string getName()=0;  //纯虚函数,返回图形的名称
};
#endif


2.派生类Circle类继承自抽象类IShape:


Circle.h头文件:


#ifndef CIRCLE_H
#define CIRCLE_H
#include"Shape.h"
class CCircle : public IShape  //公有继承自IShape类
{
public:
CCircle(float radius);  //构造函数
public:
virtual float getArea();  //实现声明实现两个基类的函数,声明的时候需要加virtual关键字,实现的时候就不需要加virtual关键字了。
virtual string getName();  
private:
float m_fRadius;  //派生类可以拥有自己的成员
};
#endif


Circle.cpp实现文件:


#include"Circle.h"
 
CCircle::CCircle(float radius)
:m_fRadius(radius)  //使用构造函数的初始化列表初始化
{
}
 
float CCircle::getArea() / /实现实现两个基类的函数
virtual string getName();  
 
{
return 3.14 * m_fRadius * m_fRadius;
}
 
string CCircle::getName()
{
return "CCircle";
}


3. 派生类Rect类继承自抽象类IShape:


Rect.h头文件:


#ifndef RECT_H
#define RECT_H
#include"shape.h"
class CRect : public IShape
{
public:
CRect(float nWidth, float nHeight);
 
public:
virtual float getArea();
virtual string getName();
 
private:
float  m_fWidth;  //矩形类具有自己的两个属性,宽和高
float  m_fHeight;
};


Rect.cpp实现文件:


#include"Rect.h"
CRect::CRect(float fWidth, float fHeight)
:m_fWidth(fWidth), m_fHeight(fHeight)
{
}
float CRect::getArea()
{
return m_fWidth * m_fHeight;
}
 
string CRect::getName()
{
return "CRect";
}


4.测试文件main.cpp:


#include<iostream>
#include"Rect.h"
#include"Circle.h"
using namespace std;
 
int main()
{
IShape* pShape = NULL; //定义了一个抽象类的指针,注意抽象类不能定义对象但是可以定义指针
pShape = new CCircle(20.2);  //基类指针指向派生类的对象
cout<<pShape->getName()<<" "<<pShape->getArea()<<endl;
 
delete pShape;  //释放了CCirle对象所占的内存,但是指针是没有消失的,它现在就是一个野指针,我们在使用之前必须对它赋值
pShape = new CRect(20, 10);  //基类指针指向派生类的对象
cout<<pShape->getName()<<" "<<pShape->getArea()<<endl;
 
return 0;
}


运行结果如下:可以看到,我们使用父类的指针调用同一个函数,分别调用了这两个派生类的对应函数,它根据指针指向的类型的不同来决定调用的方法。即使我们以后需要新增加几个类,我们还是这种调用方法,这就是多态的巨大魅力。


apply4.png



【本文由麦子学院独家原创,转载请注明出处并保留原文链接】

logo
© 2012-2016 www.maiziedu.com
蜀ICP备13014270号-4 Version 5.0.0 release20160127

您有狂欢嘉年华礼包未领取

客服热线 400-862-8862

回到顶部