构造函数,你真的弄懂了吗?
前言
看过我之前复习的随笔知道都是基础之上的语法,但是当我脑海开启回忆基础知识时,尤其是构造函数中先后执行顺序以及原因却是模棱两可,于是开始边编写边操笔来记叙下来。如果你正在学习基础语法或者是复习基础语法的路上,这篇文章或许对你亦有帮助(当然msdn也有相关定义,但是个人觉得要是看完定义后再去摸索下,或许会理解的更透彻吧)。【特此注意:高手请绕过道而走!】
继承之构造函数
首先我们定义一个Person类,并给个构造函数。代码如下:
public class Person { public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } public Person() { Console.WriteLine("父类构造函数"); } }
再写个Bob类继承该父类,同时给个构造函数,其代码如下:
接下来就在控制台实例化Bob类 Bob b = new Bob(); 看调用构造函数先后执行顺序。结果输出如下:
从这输出来看你会不会妄下结论说先调用的父类构造函数再调用子类的构造函数呢?如果你这样说的话,也就是说当我们实例化对象子类时,但是它去执行了父类的构造函数,好像有点神奇。好吧,我们接下来继续看,我们再定义一个Student类继承该Bob类。代码如下:
然后在控制台实例化Student类 Student s = new Student(); ,结果打印出:
依然是调用的父类构造函数,所以现在就下结论:子类继承父类,实例化子类对象,首先调用的是父类构造函数。好,结论似乎言之过早,我们来断点调试下不就可以得出答案所在了吗。 看下面图片,一步步断点截图:
第一步:不用说
第二步:调用子类构造函数即Bob类
第三步:调用父类构造函数即Person类
第四步:执行父类的构造函数
最后一步:执行子类构造函数
所以总结如下:子类继承父类时构造函数执行的先后顺序:
- 调用子类构造函数
- 调用父类构造函数
- 执行父类构造函数
- 执行子类构造函数
但是此时问题来了,为什么我们实例化子类对象时,最后去调用了父类的构造函数?其实是有道理可循的,我忘记了base关键字的存在,当你在子类编写构造函数时,如果没有显式的调用基类的构造函数,那么会默认在其后面添加 一个 :base() 以此来调用基类的无参构造函数。所以上述问题就解决了。
msdn上所说base关键字概念有两点:
-
调用基类上已被其他方法重写的方法。
-
指定创建派生类实例时应调用的基类构造函数。
接下来如果我们在父类中将无参构造函数修改为有参构造函数会怎样呢,这是我们再生成出现如下错误:
因为上面已经说的很清楚了,如果父类没有无参构造函数,但是子类默认会带调用父类无参的构造函数所以会出错。所以解决办法 就是在子类中构造函数显示的用 :base 来调用父类有参构造函数即可,具体不再演示。
但是问题又来了:为什么要让父类构造函数优先于子类构造函数执行呢??????请看下面给出合理解释。
我们将整个代码进行整合如下来讨论下:
public class Person { public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } public Person(string name,int age) { Console.WriteLine("父类构造函数"); } } public class Bob : Person { public Bob(string name,int age):base(name,11) (1) { this.Name = name; this.Age = age; Console.WriteLine("子类构造函数"); } } class Program { static void Main(string[] args) { Bob b = new Bob("1",12); (2) Console.ReadKey(); } }
我们只需看上述代码中标记为红色的(1)和(2),如果我们现在实例化Bob类并传参 age = 12 ,此时我们当然希望的是Bob类中age = 12,如果此时先执行子类那完了,因为有 :base(name,11) ,所以此时的 age = 11 ,这样不就造成了意想不到的结果了吗,明明传的12,结果为11,结果数据产生严重的冲突,也就是数据的不一致。如果先执行父类构造函数,此时子类age为11,但是当执行到子类构造函数时,因为我传的是12所以其age就为12。这才是我们需要的结果。
静态构造函数
我们继续就上述例子中的Person类为例进行分析,代码如下:
public class Person { public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } public Person() { Console.WriteLine("实例构造函数"); } static Person() { Console.WriteLine("静态构造函数"); } }
为了更好演示,我们实例化对象两次代码如下:
Person p = new Person(); Person p1 = new Person();
控制台运行结果如下:
从上述显示结果中至少可以看出:在调用构造函数之前就已经调用了静态构造函数。 那么我们比调用构造函数更早的时期是什么时候呢?啊,容我想想,直接声明该对象的变量(即直接第一次在加载该类下的所有成员时),不实例化就可以了。于是乎我们直接在控制台中声明变量即可,如下:
Person p2;
运行,结果是什么都没有如下:
说明此时未调用静态构造函数, 那就是在此之后,现在是调用实例构造函数之前即对象不能实例化,也就是说访问实例成员指定也是不行了,那么要是如果访问静态成员,看行不行,我们在Person类中添加
public static int age;
现在我们在控制台来访问该静态成员 Person.age = 1; 结果运行如下:
说明静态构造函数: 在类的成员第一次被访问之前,就会调用静态构造函数 。 如果还是不能理解我们直接这样做,在Person类中定义一个已经赋值的字段。如下
public int temp = 0;
此时我们在控制台中实例化对象 Person p = new Person(); ,再运行下看看结果会怎样呢?一步步调试:
第一步:显然没有调用
第二步执行到赋值的字段时:
同样也证明了上面的结果。
补充
如果对静态成员不太理解下面就静态成员定义以及静态成员和实例成员区别做一点概括吧。
静态成员
静态成员在类第一次加载时被创建。
静态成员只会被创建一次,所以有且仅有一份。
静态成员创建在静态存储区中,所以一旦被创建直到程序退出才会被回收。
静态成员与实例成员区别
生命周期:静态成员是从类第一次被加载时到程序完全退出时,但是实例成员则是在对象被创建时到该对象成为垃圾被垃圾回收器回收时。
存储位置:静态成员存储在静态存储区中,实例成员存储在堆空间相应的对象中。