bitfan(数字世界一凡人)的专栏

在新浪博客的新家:http://blog.sina.com.cn/bitfan

用户操作
[即时聊天] [发私信] [加为好友]
bitfan(数字世界一凡人)ID:bitfan
375265次访问,排名142好友0人,关注者63
=========
一名代码一写就十多年的“老古董级程序员”,看来还会再写下去;
一名有着“好为人师”臭毛病的小小讲师,“混迹于”大学校园,时不时发几句不合时宜的“反动言论”,好在皮厚,任人笑骂而无所谓;
于北京高楼林立之地租一小屋容身,依靠讲几节小课赚点小课时费应付房东大妈,所幸政府开恩照顾,终于让我等穷教师有了当房奴的资格,高呼:感谢政府!
==========
几本小书:
《网站建设教程》:高等教育出版社(2005)
《编程的奥秘——.NET软件技术学习与实践》:电子工业出版社(2006)
《.NET 2.0面向对象编程揭秘》:电子工业出版社(2007)。
《ASP.NET程序设计教程》高等教育出版社(2009)。

几堂小课:
在ITCAST(http://www.itcast.net)讲授.NET系列在线视频课程,想将课堂开到互联网上,目标不大,要帮助更多的年轻人学好技术找到好工作。野心不小,已录制了5个系列30节课,打算一路跟踪微软最新技术,打造国内自成体系、独特风格的微软技术系列课程。
bitfan的文章
原创 87 篇
翻译 0 篇
转载 0 篇
评论 2212 篇
最近评论
wjfmail:可以学,但估计要比别人多很多的努力.毕竟开发不是一种天赋就可以拥有的才能.你碰的钉子会比别人多得多.不过,还是从现在开始努力吧.
gsqswjh:我就是小学文化,而且"奔四"了,才开始学习编程,学如逆水行舟,不进则退,说的太对了,加油啊!
Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



文章分类
收藏
    相册
    .NET技术学习与实践
    5.2 使用.NET开发数据库应用程序
    数据绑定原理
    杂类
    存档
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 探讨C#2.0对象模型收藏

    新一篇: 有感于《清华计算机系旁听有感》 | 旧一篇: 清华计算机系旁听有感

    一名.NET程序员给我发了一封邮件,讨论C#2.0的对象模型:
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    金老师:
        您好!
        我是一名.net程序员。拜读了您的“.net 2.0面向对象编程揭秘”这本书,受益匪浅。
        您的书写得非常深入,经常让我有恍然大悟的感觉。很多堆积已久的问题迎刃而解。同时我也有了很多疑问。希望您能帮我解答。
     
        1.您说子类会调用父类的构造函数。同时“子类对象集成了基类的实例字段”(P213)。“基类的实例字段”包括父类的private字段么?
        
        2.Son son=new Son();
          Father father=son;
          这是您介绍的多态编程。子类实例变量赋值给父类变量。
          这样的两句代码,在内存中将会发生什么呢?
          对象的实例没有改变么?faher和son存的是同一个首地址?
          这时father能调用的都是Father类型的字段和方法。那么father的类型表指针应该指向Father类型表,这样可以解释father调用父类的方法。那么字段呢?
          father is Son
          这又是如何实现的呢?
     
       3.值类型在编译期已经在栈上分配好了内存
        编译期还是IL指令,还没有转化为cpu可执行的二进制代码。这时内存是如何分配好的?
        编译期为什么要分配内存呢?不是应该运行的时候才需要么?
     
       4.您的书中在说明IL代码的时候多次提及计算堆栈。
         您能把IL代码执行的基本原理告诉我吗?或者从哪里可以看到相关的资料。
     
       5.对象实例存储在堆中,那么对象变量呢?是存储在线程堆栈中么?
     
        同时我也发现了您的一个疏漏。
     
    第183页 第10行 首先调用父类构造函数,再调用子类构造函数。
    第211页 第6行 在构造函数中,先初始化自身的字段,在调用基类的构造函数。
    这两句表达有误。
    应该是:
    先调用子类构造函数,通过子类构造函数调用父类构造函数。先执行父类构造函数的代码,初始化父类的字段,再回到子类初始化子类的字段。
     
    我编写了这样两个简单的类
     public class Father
        {
            protected string a;
     
            public Father()
            {
                a = "a";
            }
        }
     
        public class Son : Father
        {
            private string b;
     
            public Son()
            {
                b = a;
            }
        }
     
    子类的构造函数IL代码如下:
     
    .method public hidebysig specialname rtspecialname
            instance void  .ctor() cil managed
    {
      // 代码大小       17 (0x11)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  call       instance void ConsoleApplication1.Father::.ctor()
      IL_0006:  nop
      IL_0007:  nop
      IL_0008:  ldarg.0
      IL_0009:  ldc.i4.1
      IL_000a:  stfld      int32 ConsoleApplication1.Son::b
      IL_000f:  nop
      IL_0010:  ret
    } // end of method Son::.ctor
     
    这段IL代码好很的证明了我的观点。
     
    希望您能耐心的解答我的问题。谢谢
    ++++++++++++++++++++++++++++++++++++++++++
     
    我的回复如下:
    -----------------------------
    1 “基类的实例字段”包括父类的private字段,但子类方法不能存取这个字段,这是由编译器在生成IL指令时保证的。
    2    Son son=new Son();
          Father father=son;
         实际上,在内存中只有一个Son对象实例(放在堆中),但存在两个类型表(由CLR直接管理,当卸掉程序集时,这些类型表占用的资源被回收),赋值后,father和son存的是同一个首地址,指向在托管堆中的Son对象实例。
        father is son不是在程序运行时实现的,而是在编译时实现的,由C#编译器直接将这个代码翻译为IL指令,此IL指令会根据你的代码生成对合适方法的调用指令。
        当程序运行时,CLR直接装入的是翻译好的IL指令,而非C#代码,IL指令本身是没有“多态”特性的,因为它已经比较靠近底层,应该尽可能地简化以提高效率。
     
    3 值类型在编译期已经在栈上分配好了内存
        这句是错的,变量的内存分配是在程序运行时才进行的。是我的疏漏。
     
    4 CLR可以看成是一个基于堆栈的虚拟计算机,这台机器运行的是IL汇编程序,凡是IL代码中所说的堆栈,都是指“计算堆栈(Evaluation Stack)”。在书的附录中有一个“MSIL基础教程”,其中介绍了相关的原理。有关IL的资料很少,国内可以看到的书就是《Inside Microsoft  .NET IL Assembly》,千万别看中文版,我看译者肯定没弄明白其中的技术内容,译得一踏糊涂。要看就看英文版,但这本书阅读难度很大,作者是技术牛人,但作为一名作家,我认为不合格。本书中有关IL编程的介绍是我经过收集相关资料进行消化,并经实践检验之后写的,但管中窥豹,仅供参考。
     
    5.你说得对:对象实例的数据存储在托管堆中,引用此对象的对象变量则是存储在线程堆栈中。
     
    关于子类字段与父类字段初始化顺序的问题,我注意到你在子类构造函数中使用了基类的数据成员,因此才会导致先初始化基类数据成员,后初始化子类数据成员。这是一种特例。
    事实上,如果子类字段与父类字段没有这种依存关系,C#编译器是按照以下顺序生成IL指令的:
    new 子类对象时,子类构造函数被调用,在执行子类构造函数的代码时,先初始化子类的字段,然后再调用父类的构造函数初始化父类的字段。
    如果子类字段依赖于父类字段的值,C#编译器在生成IL指令时,会先调用父类的构造函数初始化父类的字段,再调用子类构造函数初始化依赖于父类字段的这些字段。
    我修改了一下你的代码,给子类和父类增加两个独立的字段:
     
       public class Father
        {
            protected string a;
     
            public int fatherFld=100;
     
            public Father()
            {
                a = "a";
            }
        }
     
        public class Son : Father
        {
            private string b;
     
            private int sonFld = 200;
            public Son()
            {
                b = a;
            }
        }
     
    子类生成的IL代码如下:
    .method public hidebysig specialname rtspecialname
            instance void  .ctor() cil managed
    {
      // 代码大小       33 (0x21)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldc.i4     0xc8
      IL_0006:  stfld      int32 ConsoleApplication1.Son::sonFld
      IL_000b:  ldarg.0
      IL_000c:  call       instance void ConsoleApplication1.Father::.ctor()
      IL_0011:  nop
      IL_0012:  nop
      IL_0013:  ldarg.0
      IL_0014:  ldarg.0
      IL_0015:  ldfld      string ConsoleApplication1.Father::a
      IL_001a:  stfld      string ConsoleApplication1.Son::b
      IL_001f:  nop
      IL_0020:  ret
    } // end of method Son::.ctor
    可以看到IL_0006句先初始化子类的字段,IL_000c再调用父类的构造函数初始化父类字段,再回过头来于IL_0015和IL_001a两句完成用父类字段初始化子类字段的工作。
    父类构造函数IL代码如下:
     
    .method public hidebysig specialname rtspecialname
            instance void  .ctor() cil managed
    {
      // 代码大小       29 (0x1d)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldc.i4.s   100
      IL_0003:  stfld      int32 ConsoleApplication1.Father::fatherFld
      IL_0008:  ldarg.0
      IL_0009:  call       instance void [mscorlib]System.Object::.ctor()
      IL_000e:  nop
      IL_000f:  nop
      IL_0010:  ldarg.0
      IL_0011:  ldstr      "a"
      IL_0016:  stfld      string ConsoleApplication1.Father::a
      IL_001b:  nop
      IL_001c:  ret
    } // end of method Father::.ctor
    注意一下基类object构造函数的调用是插在两个字段初始化指令中间的。我经过实验发现,C#编译器生成IL代码时对于字串类型字段的初始化总是在调用基类构造函数之后,而象int之类的字段,如果是独立的,其初始化指令总在调用基类构造函数指令之前。
    为什么这样,只好去问问C#编译器的设计者了。
    ----------------------------
    欢迎就此问题进行讨论。
     
     
     
     
     

    发表于 @ 2007年12月28日 12:36:00|评论(loading...)|编辑

    新一篇: 有感于《清华计算机系旁听有感》 | 旧一篇: 清华计算机系旁听有感

    评论

    #lnf13 发表于2008-01-30 22:14:44  IP: 58.211.99.*
    说的还是不够详细 估计自己也不是很清楚
    #lnf13 发表于2008-01-30 22:16:45  IP: 58.211.99.*
    应该说作者也不能从编译的角度对多态进行详细的解释
    #lnf13 发表于2008-02-01 22:59:54  IP: 58.211.99.*
    看了楼主写的书 确实不错 牛!!!
    #A_B_C_ABC 发表于2008-04-26 13:02:30  IP: 124.172.88.*
    ===============================
    1.先调用子类构造函数,通过子类构造函数调用父类构造函数。先执行父类构造函数的代码,初始化父类的字段,再回到子类初始化子类的字段。

    2.new 子类对象时,子类构造函数被调用,在执行子类构造函数的代码时,先初始化子类的字段,然后再调用父类的构造函数初始化父类的字段。
    ===============================

    对于子类中字段和父类中的字段,先初始哪个其实是没有关系的(除非他们之间有依赖).C++是先初始化父类的字段. 但是构造函数的调用顺序一定是先调用子类的构造函数,然后由子类的构造函数调用父类的构造函数,这一点,金老师的书中表述是错误的(第183页 第10行 首先调用父类构造函数,再调用子类构造函数。).
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © bitfan(数字世界一凡人)