嗨,你好
下面我们一起来学习呢构造与析构 对象的构造呢是我们经常要用的,比如说
new 一个对象,实际上呢,它 就要构造这个对象。
那构造这个对象呢,它是用构造方法来指明它要完成的任务 也就是说它是要初始化。
那构造方法我们前面学过了,就构造方法它可以呢 调用 this
和 base ,来调用另外一个构造方法,或者是呢父类的构造方法
也就是说,任何一个类它都要调 this 和 base 构造方法,即使你是
也不写 this,也不写 base,那么它就会 自动的调用呢 base
圆括号,就是不带参数的 那这个构造方法,那像我们这个例子呢
右边这段代码它是编译通不过的,那原因是什么呢? 因为我们看,这个
B 对象有一个构造方法,这个构造方法呢 虽然我们什么也没写,但是它实际上要用
写一个冒号 base、 base 圆括号 这个
base 圆括号呢它就到父类里面来找,但是呢父类里面没有一个
不带参数的构造方法,所以呢这个程序呢就编译不能通过
当然如果我们一个类里头,既不写,不写任何构造方法,它也会自动生成一个 不带参数的构造方法。
下面我们看看呢,这个构造方法里面调用 this 和 base 的一段代码。
请看这一段代码 有一个 class Person
人这个类,那这里面呢 有一个不带参数的构造方法,也有带姓名和年龄的构造方法
那构造方法里面做的事情呢是"this.name=name ;
this.age=age" Student 类呢是 Person
的子类 它里面有一个不带参数的构造方法,这个构造方法呢调用了 this
的另一个构造方法,就是下面这个有姓名、 有年龄、 有学校 这个构造方法。
而这个构造方法呢,它又调用了 base 就是用冒号 base,就是调用 base 的构造方法。
也就是说 父类的构造方法,然后呢研究生呢它又是
学生的子类,我们呢不带参数的构造方法 那这个构造方法呢,它什么也没写
它是不是就不带构造父类的构造方法?不是的。
它就相当于自动的在这儿写了 一个 base 圆括号,相当于自动的写了这个,那这是什么原因呢?
为什么它要这么做呢?这个道理是比较好理解的,就是说我们在构造一个
子类的时候,它一定呢要把这个父类构造好,也就是说你要是
一个学生,你首先就是一个人,同样的你要是个研究生你首先是一个学生
所以呢它一定会调用父类的构造方法,使得这个对象呢,构造过程呢是比较合理的
在构造对象的时候还有一个,就是字段或者叫域,或者 叫 field 它的初始化。
那么这个 field 呢这个初始化呢,我们一般就 可以赋一个呢常量,如果你不赋值呢
它就是我们前面讲的,自动赋值为 0,或者
null,也不可以赋值为其他的,比如说 new 一个对象啊等等 但是在
C# 里头,它要求域的初始化不能引用
this 这是为什么呢?你看我们在这里,这个 int
呢是字段,然后呢 这个 M 呢是一个方法,这个 int
呢 y 等于 x 加上 M,或者是加一个变量,或者加一个这个
那这里呢它是相当于 this.M,这个呢它是编译通不过的 那
C# 为什么这么要求呢?那别的语言呢,它没有这个要求,C# 为什么有这个要求呢?
它认为呢就是我们在,初始化这个域的时候 而这个
this 这个对象本身还没构造好呢,你怎么能调用 this
的一些字段和方法呢? 所以也就说,你不能够在这个字段里面引用
this 点另一个字段,也不能引用呢 this 点另一个方法
因为这个对象都没有构造好,你哪有 this 呢,对不对?所以呢
这就是 C# 里面严格要求呢,它是有道理的。
另外一个呢 在 base 调用之前也不能引用 this
,也就是说 你看这个 base 里面,B:base,那 base
里面呢, 如果我想调用用这个 x 作为参数去调 base ,能不能行呢?
也是不行的。
因为在这个时候呢,同样的 this 这个对象呢,还没有构造好,你怎么去 把
this.x 呢,然后再去调 base 呢?所以这也是不行的
所以在这个意义上,C# 这种要求呢,还是比较严格并且呢 也是比较合理的。
现在我们来说说构造方法的执行过程 构造方法呢在执行我们知道,它既有
this,有可能调 base,所以呢 执行过程呢,还是比较复杂的。
在 C# 里面,它的执行的过程呢,就是这样的 就是如果有 this 什么什么,那个构造方法,有调
this 的构造方法的话,它就转向之,这个好理解 那主要着重注意的就是下面这个,它就首先要执行的
域的初始化,注意啊,它是并不是首先执行的是花括号里的
首先把那些字段,比如说那些字段有赋初始值的,它首先呢要执行这个 然后转到
base 里面去 然后再转到这个 base
,也就是父类的构造方法里面去 最后再执行花括号里面的,这个方法体
所以它的执行过程呢,第一句呢,比较好理解,我们不管它了,那就是一定注意它先执行字段
然后呢再执行这个 base,然后呢再执行这个方法体
在这个执行的顺序,特别是这两步呢,它是跟有些语言,比如说跟 java 呢就是不一样的
所以你一定要注意这个细节,因为这个过程它比较复杂啊 所以我们构造方法尽量避免一些复杂的事情,啊,尽量要简单
同时呢在构造方法,要避免调用任何虚方法 因为虚方法的意思就再调子类,再调子类的方法
那这个子类都没构造好啊,你怎么去调这个虚方法呢,就经常出现一些意想不到的一些问题 所以我们要避免它。
在 C# 里头啊 我们特别要指明的就是这个,先做域的初始化后转到 base
的构造函数 这个跟别的语言比如 java 语言的不同,那这两种处理究竟是
C# 这样处理好呢?还是别的语言那样处理好呢? 这样就是各有各的这个特点
也就是说这个很难说的清楚哪个好,这是因为呢我们在 C# 看呢,就是说你这个字段呢
这个内层首先要把它弄好,然后呢才转到 base 里面再去做那些复杂的事情
因为这里面它有方法体嘛,所以它认为呢字段你都没弄好,你怎么去做那些事情呢? 所以它是基于呢这么一个考虑
那像 java 语言它反过来了,它认为呢是先执行
父类的东西,那你父类都没构造好,你怎么跑来执行我这里面的呢? 所以这个呢是不同语言的处理是不一样的
所以现在有的语言,比如说 java 语言它的域的初始化可以很复杂
甚至写成语句,所以这是两种语言不同的地方 下面我们看一个例子。
好,看看这个例子,这个例子呢有点 trick 有点粘人,很绕啊。
这个 A 呢有个构造函数,这个构造函数里面呢它调用了一个
虚方法,所以我们一般的时候我们前面说呢要尽量避免,但是如果我们真要这么去做
那它就会怎么一个流程呢?它调这个虚方法,这个虚方法呢现在是 显示
A 的,啊,显示了一个、 一句话,然后呢这个 B
呢是 A 的子类 这个 B 的构造方法呢 new this(0),会调到这个这个函数。
这个函数里头 它这又显示了一句话,然后呢这里再
print 这个这个虚方法,override 这个虚方法呢 显示在这个
B 这个方法里头,然后 x 等于多少,y 等于多少 那我们在执行的时候呢
new 一个 B 那注意 new 这个 B 呢就它会调这个构造方法,但是这个构造方法呢
它又可以转到这里来,那这个构造方法呢 注意它就没有写
base,也没写 this,它相当于会自动的去调这个
base 不带参数的构造方法,所以呢它就会跑到呢
这上面去执行,这上面呢要执行这个 A 的构造方法 但在这个时候呢,它会显示
PrintFields 由于这是个虚方法,所以又跑到底下来了。
所以它是绕来又绕去,那个有机会呢 你可以试验一下,我们这里运行一下看。
啊,首先呢 它在 A 里头,首先它在 A 里头
然后呢,它这里呢构造 B 的时候呢它又调用了 base,所以它又跑到
A 里面去了 A 里面的时候呢,这个又调虚方法,所以它又会 显示呢用
B 点这个方法里头 注意这个时候呢 x 是等于 1,y
等于 0 这个是怎么来的呢?在这个时候,调这个虚方法的时候呢
x 等于 1,y 等于
0 这是因为呢字段的赋值呢,是先于我们执行函数的
base 所以它实际上这句话呢是先执行的
下面我们再来看看静态构造方法,也就是用 static 修饰的这种构造方法
那么这种构造方法呢,它只执行一次,这个是 因为它是属于类的,它不像每
new 这个对象它就执行 所以它是整个类才执行一次,但是执行的这个时机呢
是不确定的,虽然不完全确定,但是有几个呢它要保证。
第一呢 它是在所有域初始化之后执行,也就是说它是域优先字段
式优先,然后再执行这个语句,这是第一个。
另外一个呢 它总是在被使用,或者是说比如说访问它 或者生成 new
这个实例之前完成,这个道理好理解 你都要用它了,所以你这个静态构造方法,当然要先执行了
但是它的执行顺序也是不确定的,所以呢在使用构造方法
要谨慎,就不要写太复杂的东西,也要避免呢在静态初始化 或静态字段里面呢出现循环引用的情况。
出现循环引用呢虽然 它是允许的,但是这个就对我们的用户来说这是很难理解的,比如说
int a=b+1,然后你又说 static int
b=a+1 所以这个,虽然它能执行,但是这个对我们人来说
这个含义就不清楚了,所以总的说来,要尽量的简单