zoukankan      html  css  js  c++  java
  • c++ 多态

    一、多态

    即多种形态。

    类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态

    C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数

    #include <iostream> 
    using namespace std;
     
    class Shape {
       protected:
          int width, height;
       public:
          Shape( int a=0, int b=0)
          {
             width = a;
             height = b;
          }
          int area()
          {
             cout << "Parent class area :" <<endl;
             return 0;
          }
    };
    class Rectangle: public Shape{
       public:
          Rectangle( int a=0, int b=0):Shape(a, b) { }
          int area ()
          { 
             cout << "Rectangle class area :" <<endl;
             return (width * height); 
          }
    };
    class Triangle: public Shape{
       public:
          Triangle( int a=0, int b=0):Shape(a, b) { }
          int area ()
          { 
             cout << "Triangle class area :" <<endl;
             return (width * height / 2); 
          }
    };
    // 程序的主函数
    int main( )
    {
       Shape *shape;
       Rectangle rec(10,7);
       Triangle  tri(10,5);
     
       // 存储矩形的地址
       shape = &rec;
       // 调用矩形的求面积函数 area
       shape->area();
     
       // 存储三角形的地址
       shape = &tri;
       // 调用三角形的求面积函数 area
       shape->area();
       
       return 0;
    }

    结果:

    Parent class area
    Parent class area

    导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。

    但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual

    class Shape {
       protected:
          int width, height;
       public:
          Shape( int a=0, int b=0)
          {
             width = a;
             height = b;
          }
          virtual int area()
          {
             cout << "Parent class area :" <<endl;
             return 0;
          }
    };

    结果:

    Rectangle class area
    Triangle class area

    此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。

    正如您所看到的,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。

    二、虚函数

     补充:

     1.多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数;

    形成多态必须具备三个条件

    (1)、必须存在继承关系;

    (2)、继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字Virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);

    (3)、存在基类类型的指针或者引用,通过该指针或引用调用虚函数;

    2.动态联编的实现机制 VTABLE

    编译器对每个包含虚函数的类创建一个虚函数表VTABLE,表中每一项指向一个虚函数的地址,即VTABLE表可以看成一个函数指针的数组,每个虚函数的入口地址就是这个数组的一个元素。

    每个含有虚函数的类都有各自的一张虚函数表VTABLE。每个派生类的VTABLE继承了它各个基类的VTABLE,如果基类VTABLE中包含某一项(虚函数的入口地址),则其派生类的VTABLE中也将包含同样的一项,但是两项的值可能不同。如果派生类中重载了该项对应的虚函数,则派生类VTABLE的该项指向重载后的虚函数,如果派生类中没有对该项对应的虚函数进行重新定义,则使用基类的这个虚函数地址。

    在创建含有虚函数的类的对象的时候,编译器会在每个对象的内存布局中增加一个vptr指针项,该指针指向本类的VTABLE。在通过指向基类对象的指针(设为bp)调用一个虚函数时,编译器生成的代码是先获取所指对象的vtb1指针,然后调用vtb1所指向类的VTABLE中的对应项(具体虚函数的入口地址)。

    当基类中没有定义虚函数时,其长度=数据成员长度;派生类长度=自身数据成员长度+基类继承的数据成员长度;

    当基类中定义虚函数后,其长度=数据成员长度+虚函数表的地址长度;派生类长度=自身数据成员长度+基类继承的数据成员长度+虚函数表的地址长度。

    包含一个虚函数和几个虚函数的类的长度增量为0。含有虚函数的类只是增加了一个指针用于存储虚函数表的首地址。

    派生类与基类同名的虚函数在VTABLE中有相同的索引号(或序号)。

     3.

    4.父类的虚函数或纯虚函数在子类中依然是虚函数。

    有时我们并不希望父类的某个函数在子类中被重写,在 C++11 及以后可以用关键字 final 来避免该函数再次被重写。

    #include<iostream>
    using namespace std;
    class Base
    {
        public:
            virtual void func()
            {
                cout<<"This is Base"<<endl;
            }
    };
    class _Base:public Base
    {
        public:
            void func() final//正确,func在Base中是虚函数
            {
                cout<<"This is _Base"<<endl;
            }
    };
    class __Base:public _Base
    {
    /*    public://不正确,func在_Base中已经不再是虚函数,不能再被重写
            void func()
            {
                cout<<"This is __Base"<<endl;
            }*/
    };
    int main()
    {
        _Base a;
        __Base b;
        Base* ptr=&a;
        ptr->func();
        ptr=&b;
        _Base* ptr2=&b;    ptr->func();
        ptr2->func();
    }

    结果:

    This is _Base
    This is _Base
    This is _Base

    如果不希望一个类被继承,也可以使用 final 关键字

    格式如下:

    class Class_name final
    {
        ...
    };
  • 相关阅读:
    Android轻量级的开源缓存框架ASimpleCache
    ESP8266学习笔记6:ESP8266规范wifi连接操作
    javascript——正則表達式
    STL经常使用遍历算法for_each和transform的比較
    OpenGL(八)使用 subroutine 切换可编程管线
    (一二〇)CALayer的一些特性
    Android 5.0 怎样正确启用isLoggable(一)__使用具体解释
    Elasticsearch的javaAPI之query dsl-queries
    kettle使用log4j管理输出日志
    YY博客园UML用例图-活动图-状态图之博客模块
  • 原文地址:https://www.cnblogs.com/expedition/p/11366350.html
Copyright © 2011-2022 走看看