C++基础之多态

本文介绍了C++中的多态概念,包括静态多态和动态多态,重点讲解了动态多态如何通过虚函数实现。内容涵盖虚函数的工作原理、虚表的构造、函数在虚表中的次序、多继承和虚拟继承的情况。同时,通过代码验证了虚函数的实现细节,如构造函数对虚表的处理和虚函数调用机制。
摘要由CSDN通过智能技术生成

一.多态概念

  • 多态:一词最初来源于希腊语,意思是:多种形态。
  • 多态分为静态多态和动态多态。静态多态分为函数重载和泛型编程。动态多态是通过虚函数来实现的。
  • 静态多态(叫静态绑定或早绑定):编译器在编译期间完成的,,编译器可以根据函数实参的类型(可能会进行隐式的类型转换)注意:宏不是静态多态,宏是在预处理阶段完成的
  • 动态多态(又叫动态绑定或者晚绑定):在程序执行期间判断所引用对象的实际类型根据其实际类型调用相应的方法。
  • 动态绑定的条件

    • 必须是虚函数。
    • 必须通过基类类型的指针或者引用调用虚函数。
  • 纯虚函数:在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类)。抽象类不能实例化出对象纯虚函数在派生类中重新定义以后,派生类才能实例化出对象

  • 重写的概念:

    • 怎么才能构成重写?

      • 一个在基类一个在派生类。
      • 在基类的必须为虚函数,在派生类中重写时函数的原型必须一样(包括返回值,函数的名字以及形参列表),协变除外
    • 协变:

      • 若为基类 -> 返回基类的指针或者引用。
      • 若为派生类 -> 返回派生类的指针或者引用。
  • 哪些函数不可以写成虚函数?
    1.构造函数 2.拷贝构造函数 3.静态成员函数 4.友元函数 5.赋值运算符重载函数可以写成虚函数,但是最好不要这么做,使用的时候容易混淆

  • 如果类中有虚函数。那么最好将析构函数写成虚函数。

二.用代码验证有关虚函数的一些理论

1.只要类中有虚函数,编译器就会自动合成一个构造函数,并且改写构造函数,将对象的前四个字节放入虚表的地址。虚表中放着每一个虚函数的地址。

若下代码:

#include<iostream>
using namespace std;

class Base
{
public:
    virtual void Funtest1()
    {
        cout<<"Base::Funtest1()"<<endl;
    }
    virtual void Funtest2()
    {
        cout<<"Base::Funtest2()"<<endl;
    }
    virtual void Funtest3()
    {
        cout<<"Base::Funtest3()"<<endl;
    }
    void Funtest4()
    {
        cout<<"Base::Funtest4()"<<endl;
    }
    int _b;
};

typedef void (*pFun)();//定义一个函数指针
void Print( Base b)
{
    pFun * pfun =(pFun*)(*(int*)&b);
    while(*pfun)
    {
        (*pfun)();
        ++pfun;
    }
}

int main()
{
    Base b;
    Print(b);

    system("pause");
    return 0;
}

调试结果如下图:
这里写图片描述
这里写图片描述

2.虚函数是怎么完成调用的?

  • 找到虚函数表。
  • 从虚函数表中找到要调用的虚函数。

3.函数在虚标中的次序。

  • 基类:虚表中的声明次序就是在虚表中的次序。
  • 派生类:若派生类重写了虚函数,并且有属于自己的虚函数,则会以以下步骤生成虚表:

    • 先拷贝基类虚表。
    • 如果对基类虚函数重写了,则会替换(覆盖)相同位置的虚函数。
    • 在后面加上派生类自己特有的虚函数。
      上述可以通过如下代码进行验证
  #include<iostream>
using namespace std;

class Base
{
public:
    virtual void Funtest1()
    {
        cout<<"Base::Funtest1()"<<endl;
    }
    virtual void Funtest2()
    {
        cout<<"Base::Funtest2()"<<endl;
    }
    virtual void Funtest3()
    {
        cout<<"Base::Funtest3()"<<endl;
    }
    void Funtest4()
    {
        cout<<"Base::Funtest4()"<<endl;
    }
    int _b;
};
class Deriver:public Base
{
    virtual void Funtest2()
    {
        cout<<"Deriver::Funtest2()"<<endl;
    }
    virtual void Funtest5()
    {
        cout<<"Deriver::Funtest5()"<<endl;
    }

};

typedef void (*pFun)();//定义一个函数指针
void Print( Deriver d)
{
    pFun * pfun =(pFun*)(*(int*)&d);
    while(*pfun)
    {
        (*pfun)();
        ++pfun;
    }
}

int main()
{
    Base b;
    Deriver d;
    Print(d);

    system("pause");
    return 0;
}

这里写图片描述
类比以上的两个图片 可以论证以上结论。

4.多继承

class B1
{
public:
    virtual void Funtest1()
    {
        cout<<"B1::Funtest1()"<<endl;
    }
    virtual void Funtest2()
    {
        cout<<"B1::Funtest2()"<<endl;
    }
    int _b1;
};
class B2
{
public:
    virtual void Funtest3()
    {
        cout<<"B2::Funtest3()"<<endl;
    }
    virtual void Funtest4()
    {
        cout<<"B2::Funtest4()"<<endl;
    }
    int _b2;
};
class C:public B1,public B2
{
public:
    virtual void Funtest2()
    {
        cout<<"C::Funtest2()"<<endl;
    }
    virtual void Funtest3()
    {
        cout<<"C::Funtest3()"<<endl;
    }
    virtual void Funtest5()
    {
        cout<<"C::Funtest5()"<<endl;
    }
    int _c;
};

typedef void (*pFun)();//定义一个函数指针
void Print()
{
    C c;//打印B1的虚表
    pFun * pfun =(pFun*)(*(int*)&c);
    while(*pfun)
    {
        (*pfun)();
        ++pfun;
    }
    cout<<endl; 
    B2 &b=c;//打印B2虚表
    pfun =(pFun*)(*(int*)&b);
    while(*pfun)
    {
        (*pfun)();
        ++pfun;
    }
}
int main()
{
    C c;
    cout<<sizeof(C)<<endl;
    c._b1 =0;
    c._b2=1;
    c._c =2;
    Print();

    system("pause");
    return 0;
}

这里写图片描述
以上结果证明:一个类继承了几个带有虚函数的类,则会有几个虚表,而将自己特有的虚函数地址,放在第一张虚表上。

4.虚拟继承

  • 若派生类新增加一个虚函数,则创建派生类对象时会多出一张虚表来保存派生类自己的虚函数。
  • 派生类的构造函数所做的事情:
    • 调基函数
    • 填虚表地址
    • 填偏移量表格的地址
 class B
{
    virtual void Funtest1()
    {
        cout<<"B::Funtest1()"<< endl;
    }
    virtual void Funtest2()
    {
        cout<<"B::Funtest2()"<< endl;
    }
};
class C:virtual B
{
    virtual void Funtest2()
    {
        cout<<"C::Funtest2()"<< endl;
    }
    virtual void Funtest3()
    {
        cout<<"C::Funtest3()"<< endl;
    }

};

int main()
{
    C c;
    cout<< sizeof(c) <<  endl;
    system("pause");
    return 0;
}

如上述程序,若注释了Funtest3()则打印结果是16,反之则打印结果是20.故说明上述第一个理论
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值