[原创]面向对象语言的编译过程--以C++为例(二)
摘要:
概述面向对象语言的重要概念和实现技术
以C++语言为例,介绍如何将C++程序翻译成C程序
实际的编译器大都把C++程序直接翻译成低级语言程序
编译器对于继承的处理,往往是父类包含子类的对象,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | struct Base { int a; }; struct Derived { Base base; int b; }; |
再深入的偶也不会了.........
下面我们讨论对面向对象多态特性的处理。多态是面向对象语言最为精彩的地方,可以说是诞生无数神奇的特性,多态给了我们极大的自由,让我们可以在一套类的体系结构中自由游走,可以写很少的代码,但是完成复杂的功能,可以在别人的基础上做二次开发......
好了,不打广告了,下面举一个简单的例子说明编译器对多态的处理。首先介绍一下虚函数表。大多数编译器实现多态都是通过虚函数表,虚函数表是一个保存了多个函数指针的表,其中每个函数对应着一个虚函数(非虚函数不参与),调用一个虚函数的时候,编译器通过虚函数表查找相应位置的函数,所以编译时编译器不知道这个函数会调用到基类的函数还是派生类的函数,所以叫动态绑定或运行时绑定。
以下是一个简单的多态实例程序,有了虚函数所以可以在基类指针指向派生类对象的时候运行派生类的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | #include <iostream> using namespace std; class Base { public : virtual void Hello(); }; void Base::Hello() { cout<< "Hello in Base" <<endl; } class Derived: public Base { public : void Hello(); }; void Derived::Hello() { cout<< "Hello in Derived" <<endl; } int main() { Base* p= new Base; p->Hello(); //Hello in Base p= new Derived; p->Hello(); //Hello in Derived return 0; } |
---------------------------------------我是华丽的分割线------------------------------------
以下是对应的翻译结果,但是需要注意的是这仅仅是一个示例,首先pFun这个函数指针无法指向所有类型的函数,而且按照上篇文章所述,翻译Hello()函数的时候至少应该在参数里加一个本身的引用的....深入的我也不会了.....
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | #include <iostream> using namespace std; typedef void (*pFun)(); //定义函数指针 void Base_Hello() //基类虚函数 { cout<< "Hello in Base" <<endl; } pFun Base_vtf[1]={Base_Hello}; //Base的虚函数表,编译器自动把所有virtual的函数生成一个虚函数表 struct Base { Base(){vft=Base_vtf;} pFun* vft; //虚函数表指针 }; void Derived_Hello() //父类覆盖基类的函数 { cout<< "Hello in Derived" <<endl; } pFun Derived_vtf[1]={Derived_Hello}; //Derived的虚函数表 //编译器会首先复制基类的虚函数表,然后把被覆盖的虚函数用父类的虚函数代替 //然后加入父类的虚函数 struct Derived { Derived(){vft=Derived_vtf;} pFun* vft; }; int main() { //编译器会把非虚函数的引用直接翻译成对应的函数调用 //而虚函数则按照虚函数表取相应的函数,这样如果基类指针指向派生类 //那么派生类的虚函数表中相应位置已经被派生类的方法覆盖了,从而可以调用到派生类的方法 Base* p= new Base; p->vft[0](); //Hello in Base p=(Base*) new Derived; p->vft[0](); //Hello in Derived return 0; } |
从上面我们可以大致了解多态的处理过程和虚函数表的作用。我们可以用VC验证一下虚函数表。
设我们有这样的一个类:
1 2 3 4 5 6 | class Base { public : virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } }; |
按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:
1 2 3 4 5 6 7 8 | typedef void (*Fun)( void ); Base b; Fun pFun = NULL; cout << "虚函数表地址:" << ( int *)(&b) << endl; cout << "虚函数表 — 第一个函数地址:" << ( int *)*( int *)(&b) << endl; // Invoke the first virtual function pFun = (Fun)*(( int *)*( int *)(&b)); pFun(); |
实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
虚函数表地址:0012FED4
虚函数表 — 第一个函数地址:0044F148
Base::f
通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:
1 2 3 | (Fun)*(( int *)*( int *)(&b)+0); // Base::f() (Fun)*(( int *)*( int *)(&b)+1); // Base::g() (Fun)*(( int *)*( int *)(&b)+2); // Base::h() |
以上引用自:http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html非常感谢作者
下面这篇文章是使用VCdebug窗口观察虚函数的,也很不错,一并感谢作者
http://www.cnblogs.com/wirelesser/archive/2008/03/09/1097463.html
总结:本文针对面向对象的多态特性和继承特性,介绍了面向对象语言的编译过程。我们可以得到如下启示:
多态是通过虚函数表实现的,实际如果我们用sizeof看一个类的长度的话,会发现该长度实际由所有非静态成员和虚函数表(四个字节的指针)构成.
由此我们对多态有了更深刻的理解.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步