C++语言学习(十一)——多态

一、多态简介

C++中的多态(polymorphism)是指由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应。
多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性。可以减轻系统升级,维护,调试的工作量和复杂度。
多态是一种不同层次分类下的重要联系,是一种跨层操作。

二、多态实现的前提

赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。
赋值兼容是一种默认行为,不需要任何的显式的转化步骤,只能发生在public继承方式中,是多态实现的前提条件。
赋值兼容规则中所指的替代包括以下的情况:
A、子类对象可以直接赋值给父类对象
B、子类对象可以直接初始化父类对象
C、父类引用可以直接引用子类对象
D、父类指针可以直接指向子类对象
当使用父类指针(引用)指向子对象时,子类对象退化为父类对象,只能访问父类中定义的成员,可以直接访问被子类覆盖的同名成员。

#include 
using namespace std;

class Parent
{
public:
    int m;
    Parent(int a)
    {
        m = a;
    }
    void print()
    {
        cout << "Parent m = " << m << endl;
    }
};

class Child : public Parent
{
public:
    int m;
    Child(int a):Parent(a)
    {
        m = a;
    }
    void print()
    {
        cout << "Child m = " << m << endl;
    }
};
int main()
{
    Parent p(0);
    Child c(0);
    Parent p1(c);
    Parent* pp = &c;//指向子类对象的父类指针退化为父类对象
    Parent& rp = c;//指向子类对象的父类引用退化为父类对象
    pp->print(); //Parent m = 0
    rp.print(); //Parent m = 0
    //Child cc = static_cast(p);//需要转换构造函数支持
    Child* pc = static_cast(&p);
    pc->print();//Child m = xxxx
    return 0;
}

在替代后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
父类也可以通过强转的方式转化为子类,但存在访问越界的风险。
子类中可以重定义父类中已经存在的成员函数,即函数重写。
当函数重写遇到赋值兼容时,编译器只能根据指针的类型判断所指向的对象,根据赋值兼容原则,编译器认为父类指针指向的是父类对象,只能调用父类中定义的同名函数。
面向对象编程中,通常需要根据实际的对象类型判断如何调用重写函数。当父类指针指向父类对象,则调用父类定义的函数;当父类指针指向的是子类对象时,需要调用子类定义的函数;当父类引用对父类对象进行引用时,调用父类对象定义的函数;当父类引用对子类对象进行引用时,调用子类定义的函数。

三、多态形成的条件

1、多态形成的条件

根据父类指针指向的实际对象类型决定调用的函数,即多态。多态中,父类指针(引用)指向父类对象则调用父类中定义的函数,父类指针(引用)指向子类对象时调用子类对象的函数。
C++通过virtual关键字对多态进行支持,被virtual声明的函数被重写后具有多态属性。
多态形成的条件:
A、父类中有虚函数。
B、子类override(覆写)父类中的虚函数。
C、通过己被子类对象赋值的父类指针,调用共用接口。

2、虚函数

虚函数的声明语法如下:

virtual 函数声明;

虚函数的使用规则如下:
A、在父类中用 virtual 声明成员函数为虚函数。类外实现虚函数时,不必再加virtual。
B、在派生类中重定义父类中已经存在的成员函数称为函数覆写,要求函数名,返值类型,函数参数个数及类型全部匹配,并根据派生类的需要重新定义函数体。
C、当一个成员函数被声明为虚函数后,其派生类中完全相同的函数也为虚函数,派生类中的虚函数可以加virtual关键字,也可以不加。
D、定义一个父类对象指针,使其指向其子类的对象,通过父类指针调用虚函数,此时调用的是子类的同名函数。
E、构造函数不能为虚函数,在构造函数执行完毕后虚函数表指针才能被正确初始化。析构函数可以为虚函数,定义一个父类指针并使用new创建的子类对象初始化,使用delete释放父类指针的堆空间时,只会调用父类的析构函数,不会调用子类的析构函数,会造成内存泄漏,父类析构函数声明为虚函数可以避免该问题。一般来说需要将析构函数声明为虚函数。构造函数执行时,虚函数表指针未被正确初始化,因此构造函数不可能发生多态;析构函数函数执行时,虚函数表指针已经被销毁,因此析构函数也不可能发生多态。构造函数和析构函数中只能调用当前类中定义的函数版本。

#include 

using namespace std;

class Parent
{
public:
    int mi;
    void add(int i)
    {
        mi += i;
    }
    void add(int a, int b)
    {
        mi += (a + b);
    }
    virtual void print()
    {
        cout << "Parent" << endl;
    }
};

class Child : public Parent
{
public:
    int mi;
    //函数重写
    void add(int x, int y)
    {
        mi += (x + y);
    }
    //函数重写
    void print()
    {
        cout << "Child" << endl;
    }
};

int main(int argc, char *argv[])
{
    Child child;
    child.add(1,2);//调用子类函数
    child.Parent::add(1);//调用父类函数
    child.Parent::add(1,2);//调用父类函数
    Parent p = child;
    p.add(1);
    p.add(1,2);
    Parent& rp = child;
    rp.add(1);
    rp.add(1,2);
    rp.print();//Child

    Parent* pp = &child;
    pp->add(1);
    pp->add(1,2);
    pp->print();//Child

    return 0;
}

3、纯虚函数

纯虚函数的声明语法如下:

virtual 函数声明 = 0;

纯虚函数的使用规则如下:
A、含有纯虚函数的类,称为抽象基类,不可实例化,即不能创建对象,存在的意义就是被继承,提供类族的公共接口,Java中称为 interface。
B、纯虚函数只有声明,没有实现。
C、如果一个类中声明了纯虚函数,而在派生类中没有对纯虚函数进行实现,则该虚函数在派生类中仍然为纯虚函数,派生类仍然为抽象基类。

4、虚函数的使用规则

虚函数的使用规则如下:
A、只有类的成员函数才能声明为虚函数。虚函数仅适用于有继承关系的类对象,所以普通函数不能声明为虚函数。
B、静态成员函数不能是虚函数。静态成员函数不受对象的捆绑,只有类的信息。
C、内联函数不能是虚函数。
D、构造函数不能是虚函数。构造时,对象的创建尚未完成。构造完成后,才能算一个名符其实的对象。
E、析构函数可以是虚函数且通常声明为虚函数
F、含有虚函数的类,析构函数也必须声明为虚函数。在delete父类指针的时候,会调用子类的析构函数。

四、多态的意义

多态的意义如下:
A、在程序运行过程中展现出的动态特性
B、函数重写必须多态实现,否则没有意义
C、多态是面向对象组件化程序设计的基础特性
D、多态是一种跨层操作
静态联编是在程序的编译期间就能确定具体的函数调用,如函数重载。
动态联编是在程序实际运行时才能确定具体的函数调用,如函数重写。

#include 

using namespace std;

class Parent
{
public:
    int mi;
    virtual void add(int i)
    {
        mi += i;
    }
    virtual void add(int a, int b)
    {
        mi += (a + b);
    }
    virtual void print()
    {
        cout << "Parent" << endl;
    }
};

class Child : public Parent
{
public:
    int mi;
    //函数重写
    void add(int x, int y)
    {
        mi += (x + y);
    }
    //函数重写
    void print()
    {
        cout << "Child" << endl;
    }
};

int main(int argc, char *argv[])
{
    Child child;
    child.add(1,2);//静态联编
    child.Parent::add(1);//静态联编
    child.Parent::add(1,2);//静态联编
    Parent p = child;
    p.add(1);//静态联编
    p.add(1,2);//静态联编
    p.print();//静态联编
    cout << endl;
    Parent& rp = child;
    rp.add(1);//静态联编
    rp.add(1,2);//动态联编
    rp.print();//动态联编

    Parent* pp = &child;
    pp->add(1);//静态联编
    pp->add(1,2);//动态联编
    pp->print();//动态联编

    return 0;
}