偶然看到一道面试题,题目如下:
view plaincopy to clipboardprint?
1 public class A
2 {
3 public static int X;
4 static A()
5 {
6 X = B.Y + 1;
7 }
8 }
9 public class B
10 {
11 public static int Y = A.X + 1;
12 static B()
13 { }
14 }
15 class Program
16 {
17 static void Main(string[] args)
18 {
19 Console.WriteLine("X={0},Y={1}", A.X, B.Y);
20 Console.ReadLine();
21 }
22 }
1 public class A
2 {
3 public static int X;
4 static A()
5 {
6 X = B.Y + 1;
7 }
8 }
9 public class B
10 {
11 public static int Y = A.X + 1;
12 static B()
13 { }
14 }
15 class Program
16 {
17 static void Main(string[] args)
18 {
19 Console.WriteLine("X={0},Y={1}", A.X, B.Y);
20 Console.ReadLine();
21 }
22 }
要求写出结果。
当然,因为我不是在面试的情况下遇到这题,所以直接在电脑上运行了,结果是: 。回过头来想想,当执行Console.WriteLine("X={0},Y={1}", A.X, B.Y)一句之时,求取A.X的顺序在前,因此程序首先进入到A的静态构造函数中去计算X的值,而X的值依赖于B的静态成员Y,所以程序应该跳转到B中去求取Y的值,而Y=A.X+1,此时不会再一次进入A中去求取X的值,而是使用整型变量的默认值0,这样计算出来Y的值等于1,返回到A的构造函数中继续计算,得到X的结果为2,符合运行之后得到的结果。为证明我的想法,我在程序中设置断点并单步执行,执行的顺序和我想象一致。
至此,如我一般的菜鸟认为已经没什么问题了。正巧碰到一朋友,此人乃一高手,我将程序发与他,并告知自己所想。朋友看后,很快给我发会一份代码并让我猜测结果,代码如下:
view plaincopy to clipboardprint?
1 public class A
2 {
3 public static int X;
4 static A()
5 {
6 X = B.Y + 1;
7 }
8 }
9
10
11 public class B
12 {
13 public static int Y = A.X+1;
14 B()
15 { }
16 }
17
18
19 class Program
20 {
21 static void Main(string[] args)
22 {
23 Console.WriteLine("X={0},Y={1}", A.X, B.Y);
24 Console.ReadLine();
25 }
26 }
27
28
1 public class A
2 {
3 public static int X;
4 static A()
5 {
6 X = B.Y + 1;
7 }
8 }
9
10
11 public class B
12 {
13 public static int Y = A.X+1;
14 B()
15 { }
16 }
17
18
19 class Program
20 {
21 static void Main(string[] args)
22 {
23 Console.WriteLine("X={0},Y={1}", A.X, B.Y);
24 Console.ReadLine();
25 }
26 }
27
28
我大致一看,似乎还是我发过去的程序,一问之下,原来B的构造函数不再是静态了。B的构造函数体内没有任何代码,我想当然的认为结果还是和原来一样,但运行之后让我大吃一惊,结果居然成了。一个没有任何代码的空构造函数,只是由静态改为非静态,居然会得到完全不同的结果,这真是让我意想不到。看来,在程序的执行过程之上,有一些东西是我所不了解的。还好我懂得“谦受益,满招损”的道理,于是向朋友求教。在朋友的一步步指导之下,终于明白了其中的缘由,整理记录以备遗忘之时查询,也希望如我一般的菜鸟能够从中多少受益。
先考虑最简单的情况,只有Main函数所在的那一个类。我们都知道,对于控制台应用程序,Main函数是入口函数,那么,在程序进入到Main函数之前都发生了些什么呢?我们通过代码来看一看:
view plaincopy to clipboardprint?
1 class Program
2 {
3 public static int num1;
4 public static int num2 = 1;
5 public static int num3;
6 static void Main(string[] args)
7 {
8 Console.WriteLine(num2);
9 Console.WriteLine(A.num4);
10 Console.ReadLine();
11 }
12 static Program()
13 {
14 Console.WriteLine(num1);
15 num3++;
16 Console.WriteLine(num3);
17 }
18 }
1 class Program
2 {
3 public static int num1;
4 public static int num2 = 1;
5 public static int num3;
6 static void Main(string[] args)
7 {
8 Console.WriteLine(num2);
9 Console.WriteLine(A.num4);
10 Console.ReadLine();
11 }
12 static Program()
13 {
14 Console.WriteLine(num1);
15 num3++;
16 Console.WriteLine(num3);
17 }
18 }
我在第3,7,13行都设置了断点。在进入到Main函数之前,编译器会先检查所有的静态字段并给予默认值。我们运行程序,可以看到执行到num2=1的时候进入到断点。此时num1,num2,num3都被赋予了默认的值0,单步执行,进入到静态构造函数,接下来是主函数。
我们接着考虑有两个类的情况,还是先看代码:
view plaincopy to clipboardprint?
1 class Program
2 {
3 public static int num1;
4 public static int num2 = 1;
5 public static int num3;
6 static void Main(string[] args)
7 {
8 Console.WriteLine(num2);
9 Console.WriteLine(A.num4);//注意在这里引用到了类A的静态成员
10 Console.ReadLine();
11 }
12 static Program()
13 {
14 Console.WriteLine(num1);
15 num3++;
16 Console.WriteLine(num3);
17 }
18 }
19
20 class A
21 {
22 public static int num4 = 1;
23 A()//注意这里是非静态的构造函数
24 {
25 }
26 }
1 class Program
2 {
3 public static int num1;
4 public static int num2 = 1;
5 public static int num3;
6 static void Main(string[] args)
7 {
8 Console.WriteLine(num2);
9 Console.WriteLine(A.num4);//注意在这里引用到了类A的静态成员
10 Console.ReadLine();
11 }
12 static Program()
13 {
14 Console.WriteLine(num1);
15 num3++;
16 Console.WriteLine(num3);
17 }
18 }
19
20 class A
21 {
22 public static int num4 = 1;
23 A()//注意这里是非静态的构造函数
24 {
25 }
26 }
通过单步执行,可以看到首先是Program类的静态字段被赋值,然后进入到Program类的静态构造函数,执行完构造函数,程序并不是进入Main函数,而是先进入到类A,对静态字段赋值,然后才会进入到Main函数之中。至此,我们似乎可以得出结论,在进入到Main函数之前,类的静态字段会先被赋值,并且Main函数所在类的静态字段会先于类A被赋值。那么,再增加一个类会怎样呢?我们看代码:
view plaincopy to clipboardprint?
1 class Program
2 {
3 public static int num1;
4 public static int num2 = 1;
5 public static int num3;
6 static void Main(string[] args)
7 {
8 Console.WriteLine(num2);
9 Console.WriteLine(A.num4);//注意这里只引用了类A的成员
10 Console.ReadLine();
11 }
12 static Program()
13 {
14 Console.WriteLine(num1);
15 num3++;
16 Console.WriteLine(num3);
17 }
18 }
19
20 class A
21 {
22 public static int num4 = 1;
23 A()
24 {
25 }
26 }
27
28 class B
29 {
30 public static int num5 = 1;
31 B(){}//注意这里是非静态的构造函数
32 }
1 class Program
2 {
3 public static int num1;
4 public static int num2 = 1;
5 public static int num3;
6 static void Main(string[] args)
7 {
8 Console.WriteLine(num2);
9 Console.WriteLine(A.num4);//注意这里只引用了类A的成员
10 Console.ReadLine();
11 }
12 static Program()
13 {
14 Console.WriteLine(num1);
15 num3++;
16 Console.WriteLine(num3);
17 }
18 }
19
20 class A
21 {
22 public static int num4 = 1;
23 A()
24 {
25 }
26 }
27
28 class B
29 {
30 public static int num5 = 1;
31 B(){}//注意这里是非静态的构造函数
32 }
通过单步执行,我们可以看到在执行了Program的静态构造函数之后,进入到类A里边对A的静态成员进行赋值,然后直接进入到Main函数,类B中的代码并没有得到执行的机会。从编译器对代码进行优化的角度来看,这很合理,类B并不需要执行,因此单步执行也无法进入到类B中。通过在Main函数中增加Console.WriteLine(B.num5),我们可以通过单步执行进入到类B中;通过在Main函数中调节引用A.num4和B.num5的顺序,我们能够看到先引用哪个类的成员,在单步执行中就会先进入到哪个类中。据此我们可以得出结论:在被引用到类的静态成员按引用的先后顺序初始化之后,程序才进入到Main函数中。
在此之前并没有什么让人疑惑之处。接下来我们把上边的代码稍微修改下:
view plaincopy to clipboardprint?
class Program
{
public static int num1;
public static int num2 = 1;
public static int num3;
static void Main(string[] args)
{
Console.WriteLine(num2);
Console.WriteLine(B.num5);//这里引用了类B的静态成员
Console.ReadLine();
}
static Program()
{
Console.WriteLine(num1);
num3++;
Console.WriteLine(num3);
}
}
class A
{
public static int num4 = 1;
A()//注意这里是非静态的构造函数
{
}
}
class B
{
public static int num5 = A.num4+1;//类B中引用了类A的静态成员
B(){}//注意这里是非静态的构造函数
}
class Program
{
public static int num1;
public static int num2 = 1;
public static int num3;
static void Main(string[] args)
{
Console.WriteLine(num2);
Console.WriteLine(B.num5);//这里引用了类B的静态成员
Console.ReadLine();
}
static Program()
{
Console.WriteLine(num1);
num3++;
Console.WriteLine(num3);
}
}
class A
{
public static int num4 = 1;
A()//注意这里是非静态的构造函数
{
}
}
class B
{
public static int num5 = A.num4+1;//类B中引用了类A的静态成员
B(){}//注意这里是非静态的构造函数
}
我们根据先前的代码执行过程知道在进入Main函数之前会对引用到的类的静态成员进行初始化。现在我们看到,Main函数引用到的是类B的成员。根据经验,我们可能会认为,程序会先执行到类B里边,在对B的静态成员求值的时候再进入到类A里边。事情是否真的是这样呢?目前只考虑了非静态的构造函数,如果是静态的构造函数,又会得出怎样的结果呢?有心人可以自己进行实际的测试,我也会在下篇给出自己测试所得到的结果以及自己的一点看法。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/niuyongjie/archive/2009/08/23/4475950.aspx
分享到:
相关推荐
java 静态_非静态 字段_方法_代码块 子类父类构造_初始化顺序! 三个class 让你清清楚楚 第一个class java代码如下: package initialOrder; class Parent { // 静态变量 public static String p_StaticField...
它们可以被视为构造函数的补充,提供额外的初始化逻辑,特别是当多个构造函数需要执行相同的初始化操作时。 ```java public class InitFiledBlockStatic { int instanceVar; { // 对象初始化块中的代码 ...
3. **静态构造函数**:静态构造函数在类首次被引用时执行一次,可以用于初始化静态字段。静态构造函数不能被直接调用,由编译器自动处理。 在`eg3_10`这个示例文件中,可能包含了一个或多个类,其中一个类定义了...
- 静态构造函数与实例构造函数并存,两者执行时机不同:静态构造函数在类加载时执行,实例构造函数在创建实例时执行,所以它们之间不会冲突。 3. 构造函数初始化器: - 当一个构造函数需要基于另一个构造函数进行...
2. 非静态数据成员:在构造函数初始化列表中,成员变量按照它们在类声明中的顺序被初始化。即使初始化列表中的顺序不同,也会按照声明的顺序进行。 例如: ```cpp class MyClass { public: int a; int b; static...
静态构造函数主要用于初始化类的静态字段,确保在类的生命周期内只进行一次初始化。 反射API则提供了在运行时动态地获取和使用类型信息的能力。例如,`System.Reflection.Assembly`类用于加载和处理程序集,`System...
在.NET开发中,如果你需要在静态成员中实现线程安全,通常推荐使用静态初始化器(例如,`static readonly`字段的初始化)或者在首次访问静态成员时进行同步,而不是在静态构造函数中。这种方式可以避免潜在的死锁...
7. **构造函数与static字段**:静态字段属于类,不属于类的任何实例,因此不应在构造函数中初始化。它们通常在静态初始化块`{}`中初始化,或者在类加载时通过`static`关键字直接赋值。 8. **构造函数与访问修饰符**...
### Java静态数据初始化详解 #### 一、Java静态初始化...通过本篇文章的学习,我们了解了Java中静态初始化的基本概念、执行过程以及如何正确地在程序中使用静态初始化。这对于理解和优化Java应用程序具有重要意义。
静态构造函数通常用来设置类型级别的初始化,例如初始化静态字段或者创建静态资源。 再来看静态方法和实例方法。静态方法属于类,可以直接通过类名调用,不需要实例化对象。它们不能访问类的非静态成员,因为这些...
构造函数具有与类相同的名称,它通常初始化新对象的数据成员。在C#中,构造函数可以有多个,但它们的方法签名不能相同。 默认构造函数是无参数的构造函数。无论何时,只要使用new运算符实例化对象,并且不为new提供...
初始化的顺序是:首先是静态初始化块,然后是父类的非静态初始化,接着是子类的非静态初始化,最后是构造函数中的初始化代码。在`Test.java`中,可能展示了这种初始化顺序。 总结,这个压缩包文件提供了关于Java中...
静态构造函数用于对静态字段、只读字段等的初始化。静态构造函数添加 static 关键字,不能添加访问修饰符,因为静态构造函数都是私有的。类的静态构造函数在给定应用程序域中至多执行一次:只有创建类的实例或者引用...
在示例代码中,`test.b` 就是一个只读字段,它可以在静态构造函数中进行初始化,如: ```csharp public static readonly int b; ``` 在静态构造函数中,我们为 `b` 赋值: ```csharp static test() { b = 2; } `...
- 构造函数的执行顺序大致为:先分配静态成员的内存空间,然后执行静态成员初始化,接着执行静态构造函数;之后分配对象实例的内存空间,执行实例成员初始化,最后执行实例构造函数。 #### 三、静态成员与实例成员...
每个类可以有一个默认构造函数(无参数的构造器),也可以根据需求定义带参数的构造函数。构造方法的主要作用包括: 1. 初始化类的成员变量。 2. 执行必要的设置工作,比如资源分配。 3. 链接到其他构造函数,以实现...
当我们谈论“C++规定与类同名的函数就是拷贝构造函数”时,实际上是指一个类中定义的与类名相同且参数为该类类型的函数,这就是拷贝构造函数。 拷贝构造函数是一种特殊的构造函数,用于初始化一个新对象为已有对象...
最后,我们还会讨论C#中的构造函数初始化器,它允许我们在类的字段声明处指定初始化值,简化了代码并提高了可读性。 总的来说,ACCP6.0-二期的构造函数教程旨在帮助开发者掌握.NET环境下构造函数的使用,提升代码...
4. **静态字段初始化**:静态字段的初始化直接写在字段声明后的赋值语句会在静态构造函数之前执行。这是静态字段的预初始化,然后由静态构造函数进行进一步的初始化工作。 5. **唯一性**:每个类只能有一个静态构造...
}}// 输出结果:// 父类静态代码块:fatherStr(静态字段初始化值)// 子类静态代码块:childStr(静态字段初始化值)通过上述输出结果,我们可以明确父类的静态代码块先于子类的静态字段初始化执行。这是因为静态...