c++多态

2017-08-11  本文已影响0人  showaichuan

什么是多态性?

多态:相同对象收到不同消息或不同对象收到相同消息时产生不同的动作。C++支持两种多态性:编译时多态性,运行时多态性。

早绑定 vs 晚绑定

多态与非多态的实质区别就是函数地址是早绑定(静态多态)还是晚绑定(动态多态)。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。简单概括为“一个接口,多个方法”。
对于同一条命名,不同对象接到后所做出的动作是不同的,这就叫多态。

#include<iostream>
#include<stdlib.h>
#include "Circle.h"
#include "Rect.h"
using namespace std;

int main()
{
    Shape *shape1 = new Rect(3, 6);
    Shape *shape2 = new Circle(5);
    shape1->calcArea();
    shape2->calcArea();
    delete shape1;
    shape1 = NULL;
    delete shape2;
    shape2 = NULL;
    system("pause");
    return 0;
}

普通虚函数 vs 虚析构函数

普通虚函数:在类的成员函数前加virtual关键字,并在派生类中重新定义的成员函数,其作用是:当父类和子类定义有相同的成员函数,用父类的指针指向子类的对象时能够调用子类的该成员函数。因为若不加virtual关键字,那么调用的就是父类中与之同名的成员函数。
(与之区别的是隐藏,隐藏发生在子类的对象调用子类中的成员函数时而隐藏掉其父类中同名的成员函数。)
虚析构函数:在析构函数前加virtual关键字,其作用是:用父类的指针指向子类的对象并在子类中开辟一段内存时,在销毁内存时由于delete后面跟的是父类的对象,故只调用父类的析构函数而不调用子类的析构函数,从而使得子类中的内存无法释放。而虚析构函数就解决了这种内存泄漏问题。

#ifndef SHAPE_H
#define SHAPE_H
class Shape
{
public:
    Shape();
    virtual ~Shape();//虚析构函数
    virtual double calcArea();//加virtual关键字,虚函数
};
#endif 

多态中容易出现的一个问题就是:内存泄漏

若构造函数里没有从堆中申请内存,那么也就不需要在析构函数中进行释放内存操作,也就是说若构造函数什么也不做(只是打印刷存在感)的话,调用与不调用都一样

class Circle :public Shape
{
public:
    Circle(double r );
    ~Circle();
    virtual double calcArea();
protected:
    double m_dR;
    Coordinate m_pCenter;//在Circle类中定义一个指向圆心坐标的指针
};

在Circle类中定义一个指向圆心坐标的指针,则需要在Circle类的构造函数中申请内存,并且在析构函数中释放内存,从而保证内存不泄漏。
当用父类指针指向子类对象并对操作子类对象的虚函数时,是没问题的,但想借助父类指针去销毁子类对象的内存时就会出现问题,因为delete后面如果跟的是父类的指针,则只会调用父类的析构函数,若跟的是子类的指针,则既调用子类的析构函数也调用父类的析构函数。
而对于多态问题,就是用父类的指针去指向子类对象,并对子类对象进行操作,但释放的是父类的指针,此时并不调用子类的析构函数,也就无法释放子类中申请的内存,造成内存泄漏。

为解决此问题就要用到虚析构函数
即用virtual去修饰析构函数(注意是在父类的析构函数前加virtual,子类析构函数可加可不加,不加时系统会自动加上,但推荐都加上,以防子类被其他类继承变成新的父类)

class Shape
{
public:
    Shape();
    virtual ~Shape();//虚析构函数
    virtual double calcArea();
};
Circle::Circle(double r)
{
    m_pCenter = new Coordinate(x, y);
    m_dR = r;
    cout << "Circle()" << endl;
}
Circle::~Circle()
{
    delete m_pCenter;
    m_pCenter = NULL;
    cout << "~Circle()" << endl;
}

virtual在修饰函数时的一些限制:

函数指针

函数的本质就是一段二进制的代码写进内存中。可以通过一个指针指向这段代码的开头,计算机就会从开头一直执行下去,直到结尾。
先定义一个Shape类

class Shape
{
public:
    virtual double calcArea()
    {
        return  0;
    }
protected:
    int m_iEdge;
};

再定义一个Circle子类,其中并没有定义计算面积的函数,而是利用Shape中的虚函数来计算面积。那么此时虚函数是如何来实现的呢?为了弄懂这个问题,先看一个概念:虚函数表

class Circle :public Shape
{
public:
    Circle(double r);
private :
    double m_dR;
};

虚函数表

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术等。

覆盖 vs 隐藏 vs 重载

如何区分覆盖和隐藏:
如果基类中的函数和派生类中的两个名字一样的函数f
满足下面的两个条件
(a)在基类中函数声明的时候有virtual关键字
(b)基类中的函数和派生类中的函数一模一样,函数名,参数,返回类型都一样。
那么这就是叫做覆盖(override),这也就是虚函数,多态的性质
那么其他的情况呢??只要名字一样,不满足上面覆盖的条件,就是隐藏了。

所以,相同的函数名的函数,在基类和派生类中的关系只能是覆盖或者隐藏。

RTTI

运行时类型识别(Run-Time Type Identification),它提供了运行时确定对象类型的方法。
其中有两个重要的运算符:typeiddynamic_cast

纯虚函数

纯虚函数:没有函数体,且在虚函数名后面加=0
抽象类:包含纯虚函数的类,由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
接口类:仅包含纯虚函数的类。即无数据成员只有成员函数且成员函数均为纯虚函数。

接口类更多的表达的是一直能力或协议。

上一篇下一篇

猜你喜欢

热点阅读