转自:http://bbs.51cto.com/thread-1094007-1-1.html
关于子类对象的构造函数和父类构造函数的执行顺序
我们分别为父类和子类添加显式的构造函数,代码如下
我们使用VS的单步调试,来看父类和子类显式构造函数的执行顺序,如下图(动态图片,可以看到过程):
很容易的可以发现,当创建子类对象的时候
①先调用了子类的构造函数
②调用了父类的构造函数
③执行了父类的构造函数
④执行了子类的构造函数
那么为什么会这样呢?
我尝试通过反编译看源码来解释这个原因,但是反编译的结果如下,
没有发现有什么特别的地方可以解释这个原因。
最后还是查阅微软的MSDN官方文档找到了答案(原文地址点击这里)
根据微软官方的代码示例,那么下面的代码的效果也是相同的
也就是说只要在子类显式的声明了无参的构造函数,在实例化子类的对象是,子类的无参构造函数都会去调用父类无参的构造函数。
那么,如果父类没有这个无参的构造函数则会报错。
如下面的代码:
这时候编译会报错,
因为在父类中有参数的构造函数覆盖了无参数的构造函数,所以在子类的无参数的构造函数没办法回调父类的无参数的构造函数初始化父类的成员变量。所以报错。
那么在初始化子类的时候,为什么要调用父类的构造函数呢?
在初始化子类之前需要通过构造函数初始化父类的成员变量
父类的构造函数先于子类的构造函数执行的意义是什么呢?
当在父类的构造函数中和子类的构造函数中为父类的非私有成员变量赋不同默认值。当实例化子类,子类要调用构造函数初始化成员变量,如果先执行了子类的构造函数,再执行父类的构造函数,父类成员字段的值会覆盖子类成员字段的值。但是我们想得到的是子类的属性值。所以为了解决数据冲突,父类的构造函数要先于子类的构造函数执行。
如下面的代码:
这时候我们通过,声明子类对象访问strEateType的值,如下:
这里肯定是要打印出子类的属性strEateType的值,如果先执行子类构造函数对strEateType赋值,然后父类的构造函数赋值覆盖strEateType的初始值。那么打印出的将是父类成员字段的值。所以,父类的构造函数先于子类的构造函数执行。
打印结果如下:
我们分别为父类和子类添加显式的构造函数,代码如下
01 |
class Person |
02 |
{ |
03 |
private int nAge; |
04 |
protected string strName; |
05 |
double douHeight; |
06 |
public string strEateType; |
07 |
//父类的构造函数 |
08 |
public Person() |
09 |
{ |
10 |
Console.WriteLine( "我是父类的构造函数" ); |
11 |
} |
12 |
public void Hello() |
13 |
{ |
14 |
Console.WriteLine( "我可以说Hello!" ); |
15 |
} |
16 |
public void Run() |
17 |
{ |
18 |
Console.WriteLine( "我可以跑!" ); |
19 |
} |
20 |
} |
21 |
class Student : Person |
22 |
{ |
23 |
private string strClass; |
24 |
private string strAddress; |
25 |
//子类的构造函数 |
26 |
public Student () |
27 |
{ |
28 |
Console.WriteLine( "我是子类的构造函数" ); |
29 |
} |
30 |
} |
我们使用VS的单步调试,来看父类和子类显式构造函数的执行顺序,如下图(动态图片,可以看到过程):
很容易的可以发现,当创建子类对象的时候
①先调用了子类的构造函数
②调用了父类的构造函数
③执行了父类的构造函数
④执行了子类的构造函数
那么为什么会这样呢?
我尝试通过反编译看源码来解释这个原因,但是反编译的结果如下,
没有发现有什么特别的地方可以解释这个原因。
最后还是查阅微软的MSDN官方文档找到了答案(原文地址点击这里)
根据微软官方的代码示例,那么下面的代码的效果也是相同的
01 |
//子类的构造函数 |
02 |
public Student () |
03 |
{ |
04 |
Console.WriteLine( "我是子类的构造函数" ); |
05 |
} |
06 |
//这里的代码和上面的代码效果是相同的 |
07 |
public Student() |
08 |
: base () |
09 |
{ |
10 |
Console.WriteLine( "我是子类的构造函数" ); |
11 |
} |
也就是说只要在子类显式的声明了无参的构造函数,在实例化子类的对象是,子类的无参构造函数都会去调用父类无参的构造函数。
那么,如果父类没有这个无参的构造函数则会报错。
如下面的代码:
01 |
class Person |
02 |
{ |
03 |
private int nAge; |
04 |
protected string strName; |
05 |
double douHeight; |
06 |
public string strEateType; |
07 |
//父类的构造函数 |
08 |
//public Person() |
09 |
//{ |
10 |
// Console.WriteLine("我是父类的构造函数"); |
11 |
//} |
12 |
//父类的有参数的构造函数,这里覆盖了无参的构造函数 |
13 |
public Person ( string str) |
14 |
{ |
15 |
Console.WriteLine( "我是父类的构造函数{0}" ,str); |
16 |
} |
17 |
public void Hello() |
18 |
{ |
19 |
Console.WriteLine( "我可以说Hello!" ); |
20 |
} |
21 |
public void Run() |
22 |
{ |
23 |
Console.WriteLine( "我可以跑!" ); |
24 |
} |
25 |
} |
26 |
class Student : Person |
27 |
{ |
28 |
private string strClass; |
29 |
private string strAddress; |
30 |
//子类的无参构造函数 |
31 |
public Student () |
32 |
{ |
33 |
Console.WriteLine( "我是子类的构造函数" ); |
34 |
} |
35 |
public Student( string strName) |
36 |
{ |
37 |
Console.WriteLine( "我的名字叫{0}" ,strName); |
38 |
} |
39 |
} |
这时候编译会报错,
因为在父类中有参数的构造函数覆盖了无参数的构造函数,所以在子类的无参数的构造函数没办法回调父类的无参数的构造函数初始化父类的成员变量。所以报错。
那么在初始化子类的时候,为什么要调用父类的构造函数呢?
在初始化子类之前需要通过构造函数初始化父类的成员变量
父类的构造函数先于子类的构造函数执行的意义是什么呢?
当在父类的构造函数中和子类的构造函数中为父类的非私有成员变量赋不同默认值。当实例化子类,子类要调用构造函数初始化成员变量,如果先执行了子类的构造函数,再执行父类的构造函数,父类成员字段的值会覆盖子类成员字段的值。但是我们想得到的是子类的属性值。所以为了解决数据冲突,父类的构造函数要先于子类的构造函数执行。
如下面的代码:
01 |
class Person |
02 |
{ |
03 |
private int nAge; |
04 |
private string strName; |
05 |
double douHeight; |
06 |
public string strEateType; |
07 |
// 父类的构造函数 |
08 |
public Person() |
09 |
{ |
10 |
//再父类中对strEateType赋初始值 |
11 |
this .strEateType = "吃饭" ; |
12 |
Console.WriteLine( "我是父类的构造函数{0}" , strEateType); |
13 |
} |
14 |
} |
15 |
class Student : Person |
16 |
{ |
17 |
private string strClass; |
18 |
private string strAddress; |
19 |
//子类的构造函数 |
20 |
public Student() |
21 |
{ |
22 |
//在子类中对strEateType赋初始值 |
23 |
this .strEateType = "吃面条" ; |
24 |
Console.WriteLine( "我是子类的构造函数{0}" ,strEateType); |
25 |
} |
26 |
} |
这时候我们通过,声明子类对象访问strEateType的值,如下:
1 |
Student stu1 = new Student(); |
2 |
//stu1. |
3 |
string str = stu1.strEateType.ToString(); |
4 |
Console.WriteLine(str); |
5 |
Console.ReadKey(); |
这里肯定是要打印出子类的属性strEateType的值,如果先执行子类构造函数对strEateType赋值,然后父类的构造函数赋值覆盖strEateType的初始值。那么打印出的将是父类成员字段的值。所以,父类的构造函数先于子类的构造函数执行。
打印结果如下: