C++中的多态

2021年09月15日 阅读数:1
这篇文章主要向大家介绍C++中的多态,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

一,多态的理论推导ios

1.类型兼容性原则安全

  在上一节的C++中的继承中介绍了什么是类型兼容性原则。所谓的类型兼容性原则是指子类公有继承自父类时,包含了父类的全部属性和方法,所以父类所能完成的功能,使用子类也能够替代完成,子类是一种特殊的父类。因此可使用子类对象初始化父类对象,能够用父类指针指向子类对象,能够用父类引用来引用子类对象。ide

2.函数的重写函数

  函数的发生在类的继承过程当中,所谓的函数的重写是指在继承中,子类定义了与父类函数原型相同的函数,即定义了和父类中同样的函数。布局

3.类型兼容性原则赶上函数的重写spa

C++中的多态_信仰

# include<iostream>using namespace std;/* 定义父类 */class Parent
{public:    /* 定义print函数 */
    void print()
    {
        cout << "Parent print()函数" << endl;
    }
};/* 定义子类继承自父类,并重写父类的print函数 */class Child :public Parent
{public:    /* 重写父类的print函数 */
    void print()
    {
        cout << "Child print()函数" << endl;
    }
};int main()
{
    Child c;    /* 调用子类对象的print函数,打印子类的print函数 */
    c.print();    /* 经过使用做用域操做符调用父类的print函数,打印父类的print函数 */
    c.Parent::print();    /* 当咱们使用类型兼容性原则的时候,发现调用的函数是父类的print函数,这是符合编译器规则的 */
    Parent p1 = c;
    p1.print();

    Parent * p2 = &c;
    p2->print();

    Parent& p3 = c;
    p3.print();    return 0;
}

C++中的多态_信仰

输出结果:指针

 C++中的多态_故事_03

 4.类型兼容性原则赶上函数的重写的总结orm

  • 父类中被子类重写的函数依然存在于子类中,咱们能够经过做用域操做符调用父类的函数。对象

  • 子类重写父类的函数,调用子类对象时是调用的被重写的函数。继承

  • 在C++编译期间,编译器不知道父类指针指向的是一个什么对象,编译器认为最安全的方法就是调用父类对象的函数。

5.静态联编和动态联编

  • 所谓的联编就是指一个程序模块、代码之间互相关联的过程。

  • 静态联编是程序的匹配、连接在编译阶段实现,也称为早期匹配,函数的重载使用的是静态联编。

  • 动态联编是指程序联编推迟到运行时进行,因此又称为晚期联编(迟绑定),switch和if语句是动态联编的典型例子。

6.类型兼容性原则和函数重写所带来的问题

  • 首先编译器的这种作法是咱们所不指望的。

  • 咱们须要的是根据实际的对象类型,来调用实际的函数。

  • 若是父类指针(引用)指向(引用)的是父类对象,则调用父类对象的函数。

  • 若是父类指针(引用)指向(引用)的是子类对象,则调用子类对象的函数。

7.针对上述问题的解决

  针对上面的问题,C++提供了一套解决方案来实现上述咱们的指望,新航道托福经过使用virtual关键字来修饰被重写的函数后,便可以实现咱们上述的问题。

8.多态的代码示例

C++中的多态_信仰

# include<iostream>using namespace std;/* 定义父类 */class Parent
{public:    /* 定义print函数,使用virtual关键字修饰 */
    virtual void print()
    {
        cout << "Parent print()函数" << endl;
    }
};/* 定义子类继承自父类,并重写父类的print函数 */class Child :public Parent
{public:    /* 重写父类的print函数 */
    virtual void print()
    {
        cout << "Child print()函数" << endl;
    }
};int main()
{
    Child c;    /* 调用子类对象的print函数,打印子类的print函数 */
    c.print();    /* 经过使用做用域操做符调用父类的print函数,打印父类的print函数 */
    c.Parent::print();    /* 调用父类对象的函数发现当父类指针(引用)指向(引用)子类对象时,调用的是子类对象的函数,元素除外 */
    Parent p1 = c;
    p1.print();

    Parent * p2 = &c;
    p2->print();

    Parent& p3 = c;
    p3.print();    return 0;
}

C++中的多态_信仰

输出结果:

C++中的多态_计划_06

9.多态案例的分析

  • 使用virtual关键字的函数被重写后就会根据实际的对象类型指向对应的函数。

  • 所谓的多态就是指一样的调用语句会有多种不一样的表现状态。

  • 父类指针指向子类对象和父类对象引用子类对象才能够实现所谓的多态,而父类元素被子类对象初始化是不展现多态特性的。

10.多态成立的条件

  • 存在继承关系。

  • 父类函数为virtual函数,而且子类重写父类的virtual函数。

  • 存在父类的指针(引用)指向(引用)子类对象。

二,多态的原理探究

1.多态原理基础知识

  • 当类中声明了虚函数(即virtual关键字修饰的函数)后,编译器会根据类生成一张虚函数表。

  • 虚函数表是一张用来存储类中虚函数指针的表,虚函数表由编译器自动生成和维护。

  • 当类中存在虚函数时,每一个对象都会存在一个指向虚函数表的vptr指针。雅思词汇编译器会给父类对象,子类对象提早布局vptr指针,当调用对应的函数时,会根据vptr指针所指向的虚函数表来查找相应的函数并调用。

  • vptr指针是指向虚函数表的指针,vptr指针通常做为类对象的第一个成员。

2.多态实现原理图示

C++中的多态_计划_07

3.多态原理说明

  • 经过虚函数表的指针vptr在运行时调用相应的虚函数表中的函数,所以多态是动态联编。根据vptr寻找虚函数表是寻址操做,找到后再调用相应的函数。而普通的成员函数则是在函数编译时就已经肯定了要调用的函数,所以在执行效率上虚函数要慢许多,因此出于效率的考虑,没有必要将类中的全部成员函数声明为虚函数。

  • C++编译器在运行期间,不须要区分对象时子类对象仍是父类对象,而只是 用相应的vptr指针来调用相应的函数而已,所以形成了这种虚假的多态现象。

4.vptr指针的存在证实

C++中的多态_信仰

# include<iostream>using namespace std;class Test1
{public:    /* 虚函数 */
    virtual void test()
    {
        cout << "vptr指针的存在证实" << endl;
    }
};class Test2
{public:    /* 普通成员函数 */
    void test()
    {
        cout << "vptr指针的存在证实" << endl;
    }
};int main()
{
    Test1 t1;
    Test2 t2;

    cout << "Test1 sizeof = " << sizeof(t1) << endl;
    cout << "Test1 sizeof = " << sizeof(t2) << endl;    return 0;
}

C++中的多态_信仰

输出结果:

  咱们发现含有虚函数的类的对象包含了4个字节,说明存在一个vptr指针,由于指针大小即4个字节。

5.vptr指针的建立时机

  vptr指针是在对象构造函数结束以后才建立的,而后指向虚函数表。

三,虚析构函数

1.虚析构函数的做用

  当咱们在开发父类的时候,一般会把父类的析构函数声明为虚函数,由于在继承中,当咱们delete释放内存的时候,子类的对象的析构函数不会去执行,咱们须要将父类的析构函数显式的声明为虚函数才会让子类的析构函数去调用执行。

2.案例演示

C++中的多态_信仰

# include<iostream>using namespace std;class MyParent
{public:
    MyParent()
    {
        cout << "MyParent构造函数" << endl;
    }    /* 父类的析构函数通常声明为虚析构函数 */
    virtual~MyParent()
    {
        cout << "MyParent析构函数" << endl;
    }
};class MyChild:public MyParent
{public:
    MyChild()
    {
        cout << "MyChild构造函数" << endl;
    }    ~MyChild()
    {
        cout << "MyChild析构函数" << endl;
    }
};int main()
{    /* 若是删除父类的虚析构函数,则子类的析构函数不会被调用执行 */
    MyParent * p = new MyChild;    delete p;    return 0;
}

C++中的多态_信仰

四,函数的重载和重写的区别

1.函数的重载

  • 必须在同一个类中。

  • 子类没法重载父类的函数,父类的同名函数会被子类名称相同的函数覆盖,但不会发生重载。

  • 重载是编译期间根据函数的参数个数和参数类型决定调用哪一个函数。

2.函数的重写

  • 函数的重写必须发生在继承中。

  • 父类和子类的函数原型必须相同。

  • 使用virtual关键字的父类函数才能成为函数的重写,不然叫作函数的重定义。

  • 函数的重写调用时是在运行期间调用,属于动态联编,是根据实际对象的vptr指针所指向的虚函数表决定调用哪一个函数。