none
为什么下面这段程序y输出的值是0而不是-1呢 RRS feed

  • 问题

  • using system;
    class a
    {
    public a(){
    printfields();
    }
    public virtual void printfields(){}
    }
    class b:a
    {
    int x=1;
    int y;
    public b(){
    y=-1;
    }
    public override void printfields(){
    console.writeline("x={0},y={1}",x,y);
    }
    当使用new b()创建b的实例时,产生什么输出?
    答:x=1,y=0
    为什么y的结果是0而不是-1呢,b类中不是有构造函数使y=-1了吗,请高人指点,感谢
    2010年4月27日 14:40

答案

  • 先分清几个概念,以上面代码为例, int y 仅表示声明y, y=-1 表示赋值,int x=1 是前面两者的结合,即声明 int 的 x 并以1初始化

    C# 对象创建的时候,会将 int x=1  这样的字段初始化内联进每个构造函数进行赋值,并在调用基类构造函数之前执行,然后再调用本身的构造函数内的代码,也就说你上面的 b() 构造函数编译之后大概是:

    b() {
    x = 1;
    a(); // 此时 x=1, 但 y 保持缺省值即0,
    y=-1;
    }

    建议你通过 IL DASM 查看编译之后的 IL 就能清楚看到上面这一点

    此外,实际项目中,这种根究语言特性的代码最好不要出现,因为很难读懂,只会考试才取挖这种边边角角,但还是要知道的,这样我们才能避免出现这种晦涩难懂的代码


    问题要简单,错误须详细@错误/异常/堆栈信息+操作系统+软件版本+all the context of the issue Hope Helpful | http://www.leoworks.net
    2010年4月27日 17:48
  • 你好!

         在调用子类的构造函数之前,需要先调用基类的构造函数,这一点你应该已经清楚了,下面我用注释解释一下:

     class b:a
    {
    int x=1; //这里标准名称是初始化器,这个初始化会在调用基类的构造函数之前执行
    int y;
    public b(){
    y=-1; //这行代码会在调用基类的构造函数之后执行,所以当调用printfields打印的时候,y并没有被赋值为-1
    }

    实际上这个题目主要就是想让你搞清楚初始化过程的每一步的执行顺序!


    周雪峰
    2010年4月28日 1:56
    版主

全部回复

  • 先分清几个概念,以上面代码为例, int y 仅表示声明y, y=-1 表示赋值,int x=1 是前面两者的结合,即声明 int 的 x 并以1初始化

    C# 对象创建的时候,会将 int x=1  这样的字段初始化内联进每个构造函数进行赋值,并在调用基类构造函数之前执行,然后再调用本身的构造函数内的代码,也就说你上面的 b() 构造函数编译之后大概是:

    b() {
    x = 1;
    a(); // 此时 x=1, 但 y 保持缺省值即0,
    y=-1;
    }

    建议你通过 IL DASM 查看编译之后的 IL 就能清楚看到上面这一点

    此外,实际项目中,这种根究语言特性的代码最好不要出现,因为很难读懂,只会考试才取挖这种边边角角,但还是要知道的,这样我们才能避免出现这种晦涩难懂的代码


    问题要简单,错误须详细@错误/异常/堆栈信息+操作系统+软件版本+all the context of the issue Hope Helpful | http://www.leoworks.net
    2010年4月27日 17:48
  • 你好!

         在调用子类的构造函数之前,需要先调用基类的构造函数,这一点你应该已经清楚了,下面我用注释解释一下:

     class b:a
    {
    int x=1; //这里标准名称是初始化器,这个初始化会在调用基类的构造函数之前执行
    int y;
    public b(){
    y=-1; //这行代码会在调用基类的构造函数之后执行,所以当调用printfields打印的时候,y并没有被赋值为-1
    }

    实际上这个题目主要就是想让你搞清楚初始化过程的每一步的执行顺序!


    周雪峰
    2010年4月28日 1:56
    版主
  • 我要特别说明一下这个问题。这个问题没有这么简单。

    在您给出的代码中,基类的构造函数调用了一个虚函数。在继承类中,虚函数利用控制台写了一行文本,以输出 x, y 的值。

    x 的值很好理解,就是 1,因为有 int x = 1。

    y 的值在类初始化时被赋值为 0 (default(int)),然后执行 B 的构造函数,B 的构造函数会执行 A 的构造函数,而 A 的构造函数会执行虚函数,此时,执行虚函数的调用方是运行时才指定的,也就是此时的 B,于是开始输出 x, y,此时 x = 1, y = 0,因此就会有如上的输出。程序或许会继续执行,y = -1,则最终结果可能是 x = 0,y = -1。

    注意,我用了或许将继续执行这行字。大多数情况下,在基类类的构造函数中调用虚函数,然后实例化它的一个非密封类的实例,会导致此类的构造函数有时不执行。因为虚函数的行为是运行时才指定的。

    这个问题也就成了不要在构造函数中使用虚函数

    请参考 MSDN 的文档,这里有一些解释。http://msdn.microsoft.com/en-us/library/ms182331(VS.80).aspx

    所以我认为,出题的这个人有可能是个专家,或者,他的题目就有问题,他也没有想到会有这样的结果。

     


    Mark Zhou
    2010年4月28日 12:55
  • 你好!Mark Zhou!

       我看了你给出的参考链接:

    When a virtual method is called, the actual type that executes the method is not selected until run time. When a constructor calls a virtual method, it is possible that the constructor for the instance that invokes the method has not executed.

       文档的意思好像是说,这个在调用虚方法的时候,这个实例的构造器可能还没有执行,而不是说不执行的意思吧!

     


    周雪峰
    2010年4月28日 14:54
    版主
  • 是的。文档上说了可能没有执行。不过,我就遇到过调用虚函数的继承类构造函数执行到一半终止的问题。

    不要太相信 MSDN 文档库。。。有些深层次的问题,要去找专家去。呵呵。

    比如,MSDN 说,值类型对象分布在栈上,引用类型对象分布在堆上。这个。。。是有问题的。


    Mark Zhou
    2010年4月29日 10:28
  • Mark,你好!

         感谢你的回复啊!你的意思是说继承类的构造函数更本不执行,是这个意思吗?

         到底怎样的情况才能导致继承类的构造函数不执行呢?

         期待着你的回复!


    周雪峰
    2010年4月29日 11:10
    版主
  • 不是不执行,可能执行了一半。

    我记得有一个 Scenario 就是基类有个构造函数,一个虚函数,基类构造函数调用了此虚函数;继承类重写了虚函数,重写的代码中有用到 using 或者 multithreading,结果执行时这个子类的虚函数出异常,然后它的构造函数跑了一半,实例出来了但是其实就是构造函数很多代码都没有执行。

    这个问题发生在四五年前吧。。。最近几年因为都要 FxCop 100% 通过所以没有遇到过这类问题了。


    Mark Zhou
    2010年4月30日 9:36
  • 感谢你的回复!

    我清楚你的意思了!


    周雪峰
    2010年4月30日 9:43
    版主
  • 嗯。其实还有一种可能,就是让 CLR 抛 StackOverflowException 然后用 catch 去抓。这时,catch 是永远 catch 不到那个 StackOverflowException 的。这个一定会导致程序终止并没有任何错误出来。这个问题我和我同事已经验证过了。

    关于如何让 CLR 抛 StackOverflow 的问题,也很简单。比如

    public class Foo
    {
        public Foo Bar { get; set; }
        public Foo() { this.Bar = new Foo(); }
    }

    你可以试试看。


    Mark Zhou
    2010年4月30日 9:55
  • 你好!

    是啊!

    从.NET Framework开发就无法使用try...catch来捕获StackOverflowException了!


    周雪峰
    2010年4月30日 12:32
    版主