C++多态和虚表浅析

来源:互联网 发布:备案的域名 编辑:程序博客网 时间:2024/06/16 17:43
(一)说多态前先说下对象的类型
    对象的类型两种:什么是动态类型?什么是静态类型?
    1静态类型:对象声明时的类型,在编译时确定
    2动态类型:目前所指对象的类型,在运行时确定
  动态是用new动态申请内存的~~new出来的内存需要手动释放~~
  比如说  int a=new int(10)~也就是用delete来释放~~
  另外~动态分配的内存是在堆上面的~~~内存有~堆  栈  和常量存储区~
  静态是一般的类型~~比如说int a=10;内存分配在栈上~~
  另外栈内存上的数据是有顺序的~~先进后出~~
  而在堆内存上是无序的~~所以需要指针来操作~~万一指针丢了~~或者忘记释放内存~是很麻烦的~~
(二) 多态分为静态多态和动态多态
        1静态多态含有函数重载和泛型编程
静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编错误。
#include<iostream>
using manespace std;
int Add(int left ,int right)
    {
return left + right;
    }
float Add(float left ,float right)
    {
return left + right;
    }
        int main
{
cout<<"Add(10,20)"<<endl;
cout<<"Add(10.33f,22.34f)"<<endl;
return 0;
}


运行结果是30
 32.67
像这种重载就属于静态多态
 
        动态多态含虚函数:
        动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
        使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
#include<iostream>
#include<Windows.h>
using namespace std;
class WashRoom
{
public:
void GOTOMANwashroom()
  {
cout<<"男人左转"<<endl;
}
void GOTOWOMANwashroom ()
{
cout<<"女人右转"<<endl;
}


}
    class Person
{
public:
virtual void GOTOwashroom(WashRoom& washroom)=0;


};
class MAN:public Person
{
virtual voidGOTOwashroom(WashRoom& washroom)
{
washroom.GOTOMANwashroom();
}
};
     class WOMAN:public Person
{
virtual voidGOTOwashroom(WashRoom& washroom)
   {
washroom.GOTOWOMANwashroom();
   }
};
    void FunTest()
{
      WashRoom washroom;
     for ( int iIdx = 1 ; iIdx <= 10 ; ++iIdx)
       {
          Person* pPerson;
          int iPerson = rand ()%iIdx;
         if (iPerson&0x01)
         {
           pPerson = new MAN ;
          }
         else
        {
           pPerson = new WOMAN ;
         }
         pPerson->GOTOMANwashroom(washroom);
         delete pPerson;
         pPerson = NULL ;
         Sleep(1000 );
       }
}
int main()
{
FunTest();
return 0;
}
输出  男人左转 女人右转 男人左转 女人右转 男人左转 女人右转 男人左转 女人右转 男人左转 女人右转 ,每个输出之间间隔1秒左右


【动态绑定条件】

 1、必须是虚函数
 2、通过基类类型的引用或者指针调用虚函数


(三)先看一段代码
#include<iostream>
using namespace std;
class animal
{
public :
virtual void eat()
{
cout << "animal eat" << endl;
}
void sleep()
{
data1 = 10;
cout << "this=" << this << endl;
cout << "animal sleep" << endl;
}
private:int data1;
};
class dog:public animal 
{
virtual void eat()
{


cout << "dog eat" << endl;
}
void sleep()
{
cout << "dog sleep" << endl;
}
private:int data2;
};
int main()
{
dog d;
animal *a = &d;
a->eat();
a->sleep();
cout << sizeof (animal) << endl;
cout << sizeof(dog) << endl;
system("pause");
return 0;
}
输出结果是




      第一个输出是因为
      C++编译器在编译的时候,要确定每个对象调用的函数(要求此函数是非虚函数)的地址,这称为早期绑定,

      当我们将dog类的对象d的地址赋给a时,C++编译器进行了类型转换,此时C++编译器认为变量a保存的就是      animal对象的地址。当在main()函数中执行a->eat()时,调用       的当然就是animal对象的eat函数。

      第二个输出的是this指针的地址

      第三个输出是因为父类有虚函数子类会将其重写,运行时再去确定对象的类型以及正确的调用函数,除非是显示访问不然只会访问子类成员函数

       第四个和第五个对比可以看出子类大小比父类大4 因为子类又多了个data2

     还有要注意纯虚函数在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类        中重新定义以后,派生类才能实例化出对象。


  (四)对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针

class CTest
{
public:
CTest(){ iTest = 10; }
/*virtual */~CTest()

{

cout<<"this="<<this<<endl;

};

private:
int iTest;
};
int main()
{
cout << sizeof(CTest) << endl;
system("pause");
return 0;

输出结果


看监视窗口和反汇编


转到下一步时


add 一个4

验证了对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针

(五)虚表分析

#include<iostream>
using namespace std;
class CBase
{
public:
virtual void FunTest0(){ cout << "CBase::FunTest0()" << endl; }
virtual void FunTest1(){ cout << "CBase::FunTest1()" << endl; }
virtual void FunTest2(){ cout << "CBase::FunTest2()" << endl; }
virtual void FunTest3(){ cout << "CBase::FunTest3()" << endl; }
};
class CDerived :public CBase
{
public:
virtual void FunTest0(){ cout << "CDerived::FunTest0()" << endl; }
virtual void FunTest1(){ cout << "CDerived::FunTest1()" << endl; }
virtual void FunTest4(){ cout << "CDerived::FunTest4()" << endl; }
virtual void FunTest5(){ cout << "CDerived::FunTest5()" << endl; }
};
typedef void(*_pFunTest)();
void FunTest()
{
CBase base;
for (int iIdx = 0; iIdx < 4; ++iIdx)
{
_pFunTest pFunTest = (_pFunTest)(*((int*)*(int *)&base + iIdx));
pFunTest();
}
cout << endl;
CDerived derived;
for (int iIdx = 0; iIdx < 6; ++iIdx)
{
_pFunTest pFunTest = (_pFunTest)(*((int*)*(int *)&derived + iIdx));
pFunTest();
}
}
void TestVirtual()
{
CBase base0;
CDerived derived;
CBase& base1 = derived;
}
int main()
{
FunTest();
TestVirtual();
system("pause");
return 0;
}

输出结果是


派生类的结构


派生类虚表的形成

1先拷贝基类的虚表

2如果派生类重写了基类虚函数,则将同位置基类虚函数修改

3最后是派生类新定义的虚函数



多重继承时


0 0
原创粉丝点击