今天这节课我们来学习类的构造方法和析构方法,同学们现在回忆一下在类一 系列中,学习到创建一个类ClassName的实例,也就是对象ObjName时基本语法为:ClassName ObjName=new ClassName();我说过,new后面的ClasName()实际上指的是这个类的构造方法,而且我也说过,这个()中可以有参数,这个有参数,就是指构造方法可以有参数,那么什么是类的构造方法呢?
构造方法
构造方法又叫构造函数,也有人叫做构造器,其实就是对类进行初始化。构造方法是一种特殊的方法,在类实例创建之前执行,用来初始化对象,完成对象创建前所需的相关设定,构造方法允许将类实例初始为有效状态的特殊方法,这就是构造方法的定义,用通俗的话说,就是开车前的暖车工作,用洗衣机之前的接上电源的工作,参数可以有多个可以这样理解,洗衣机的插头有两头的、有三项的,在创建洗衣机对象的时候,要分清插头的种类,才能创建成功对象。
为什么说构造方法是特殊的方法呢?因为构造方法本身没有返回值,并且通常是public访问类型,方法的名称必须与类名相同,当我们没有明确的在类中定义构造方法的时候,例如我们以前所定义的类,都是没有定义构造方法的,这时系统会使用默认的构造方法,如创建ClassName类的默认构造方法,public ClassName(){}。默认的构造方法并没有进行任何类初始化行为,你可以自己定义构造方法,当然如果你有自定义构造方法,那么默认的构造方法就会失效了。也就是说,当我们在ClassName类中没有定义构造方法时,C#语言会生成一个空的构造方法ClassName(),当然这个空的方法是什么也没做,只是为了让我们定义的类能够在创建对象时顺利的实例化而已。
构造方法可以有两个,因为参数的不同区别开,这就构成了方法的重载 ,方法重载的最大的好处在与可以利用相同的名称来组织应用程序的方法成员,当一个复杂的类被创建的时候,对功能相同的方法最好采用相同的命名方式,用不同的参数来区别,比如,计算面积时,我们就可以把四边形面积的计算方法的名字起同一个,用参数来区别如正方形定义一个参数(一个边长),长方形定义三个参数(一个长,一个宽,另一个长方形的标志如0),梯形定义三个参数(一个底,一个高,另一个梯形的标志如1),通过第三个参数标志区别长方形和梯形不同的面积公式。方法的重载要注意的是,方法名相同,但是参数类型或个数必须要有所不同,方法重载的优点就是可以在不改变原方法的基础上,新增功能。
下面我们来看实例,通过实例我们来体会构造方法是如何在创建对象时初始化的。
构造方法实例 namespace hello { // 创建一个人类Ren class Ren { /* 观察字段成员如果不赋初值时,在创建对象的时候, * 编译器会按照类型默认的给变量一个初值,如年龄、姓名 * 我们可以在构造方法中,对未赋值的字段进行改变,如年龄 * 如果字段已经赋初值了,那么创建对象的时候, * 就会使用初值,如果需要改变,就在构造方法中重新赋值即可,如性别 */ // 定义一个年龄变量,不赋初值 int age; // 定义一个姓名变量,不赋初值 string name; // 定义一个性别变量,赋初值为男 string Sex = " 男 " ; // 定义一个方法,读取类中字段成员,值的变化。 void Say() { Console.WriteLine( " 我的年龄:{0},我的名字:{1},我的性别:{2} " , age, name,Sex ); } /* 1.以下方法都为Ren这个类的构造方法,他们因为参数不同,形成了方法的重载, * 2.this关键字:this关键字用来引用对象本身,已经是说,this.name指的是对象中的name字段, * 而=name的这个name指的是方法中离它定义最近的name,这里值的是构造方法中传入的name参数 * this关键字在例子结束后,还会具体的讲解。 */ // 在无参的构造方法中,给性别重新赋值,年龄和姓名按照创建对象时默认的赋初值 public Ren() { // 改变性别的值 Sex = " 女 " ; Console.WriteLine( " 我刚出生!还没有名字,年龄也从0岁开始! " ); // 此时读取对象中的字段值的变化,应该性别改变,年龄和姓名都为默认值 Say(); } // 创建一个带姓名参数的构造方法,将创建对象时传入的name参数赋给对象中的字段name值,这样say方法中的姓名也有了改变 // 同时在这个构造方法中,给age字段自定义赋值。 public Ren( string name ) { this . name = name ; Console.WriteLine( " 呦!我使用双截棍,我有名字了!,但是年龄呢?,我自己定,我就27岁了。 " ); age = 27 ; Say(); } // 创建一个带姓名参数、姓名的构造方法,将创建对象时传入的name参数赋给对象中的字段name值, // 将创建对象时传入的age参数的值赋给对象中的字段age,这样say方法中的姓名、年邻都有了改变 public Ren( int age, string name) { this .age = age; this .name = name; Console.WriteLine( " 哦!你给我起名字,告诉我多大了吧!你说的算! " ); Say(); } // 定义静态的构造函数,我把她放在在最后,但是会第一个被调用。 static Ren() { Console.WriteLine( " 我是静态的构造函数,不能有参数及访问修饰符,并且创建对象时,我只执行一次,最先调用 " ); Console.WriteLine(); } } class Program { static void Main( string [] args) { // 创建无参的对象boby,并且创建时会先调用静态的构造方法,再调用无参的构造方法 Ren boby = new Ren(); Console.WriteLine(); // 创建有姓名参数的对象songer歌手 Ren songer = new Ren( " 周杰伦 " ); Console.WriteLine(); // 创建有年龄、姓名参数的对象man Ren man = new Ren( 33 , " 贝克汉姆 " ); Console.WriteLine(); } } }
结果如下
我是静态的构造函数,不能有参数及访问修饰符,并且创建对象时,我只执行一次,最先调用
我刚出生!还没有名字,年龄也从0岁开始! 我的年龄:0,我的名字:,我的性别:女
呦!我使用双截棍,我有名字了!,但是年龄呢?,我自己定,我就27岁了。 我的年龄:27,我的名字:周杰伦,我的性别:男
哦!你给我起名字,告诉我多大了吧!你说的算! 我的年龄:33,我的名字:贝克汉姆,我的性别:男
在上面的例子中同学们会观察到我定义了一个static Ren(){}的构造方法被称为静态构造方法。构造方法的重载中还包括一种,在方法的名字前的修饰符只有static,称为静态构造方法 ,使用静态构造方法要注意以下几点:
1.静态构造方法只有一个;
2.并且没有参数;
3在所有的构造方法中最先被执行;
4.静态的构造方法不会被继承,因为它的修饰符是私有的并且只能是私有的;
5.静态构造方法在所有静态成员本初始化后执行,也就是说如果在类中定义了静态成员,就会自动生成一个静态构造方法,否则如果要使用静态构造方法就得自定义。
6.静态构造方法在所有静态成员被引用之前执行。
7.静态构造方法在所有实例成员被分配之前执行。
本例中还出现了this关键字,下面我们来说说this关键字 ,this关键字最大的用途就是用来区分类级别的变量和局部变量,当一个位于方法内部的局部变量与类级别的变量名称相同的时候,因为有效区域不同,因为并不不会发生冲突,但是如此一来,在方法内部所访问的变量,一定是其中所定义的局部变量,而非类级别的同名变量,在这种情况下,我们就使用this关键字,比如第一个实例中,用this代表对象本身,this.name指的是类级别中定义的name字段.使用this虽然可以让我们分辨出不同级别的变量,但是最好还是起不同的名字来区分变量。this关键字还有一种用法,就是在同类的构造方法中,指代无参的构造函数,使用:this()来实现继承。下面我们来看静态构造方法和this关键字的构造方法继承用法实例:
静态构造方法和this关键字 1 class StaticTest 2 { 3 // 如果i没有初值,编译器不会自动创建静态构造方法。 4 public static int i = 1 ; 5 6 // 创建自定义的静态构造器,观察它的执行顺序。 7 static StaticTest() 8 { 9 Console.WriteLine(" 我应该在1的前面被调用,我只被调用一次。 " ); 10 }11 12 // 定义私有的实例变量j和s,不赋初值 13 int j; 14 string s; 15 16 // 定义无参的构造器,同时给j和s赋值 17 public StaticTest() 18 { 19 Console.WriteLine(" 我是无参构造器!,我给j赋值后,只要继承我,使用:this(),你们的j和s都是这个值 " ); 20 j = 10 ; 21 s = " 原来的值 " ; 22 }23 24 // 通过:this()方式,继承了j和s的值 25 26 public StaticTest( string ss): this () 27 { 28 // 此时j=10,如果没有继承,j=0. 29 Console.WriteLine( " j= " + j); 30 31 // 通过传参进入的值,改变了继承了无参构造器中s的值, 32 // 说明即使继承了s的值,也可以在本构造方法中改变s的值 33 s = ss; 34 Console.WriteLine(s);35 36 }37 }38 class Program 39 { 40 static void Main( string [] args) 41 { 42 // 先调用静态成员i的值,结果会是先执行静态构造方法,再显示i=1; 43 Console.WriteLine( " 用我前,先会查看是否有静态构造方法的定义,如果有先执行它,再得出i= " + StaticTest.i); 44 Console.WriteLine(" ------------- " ); 45 46 // 此时不会再出现static构造器的内容,因为前面已经执行了一次。 47 StaticTest A = new StaticTest(); 48 Console.WriteLine(" ------------- " ); 49 50 // 观察j和s值的变化: 51 StaticTest B = new StaticTest( " s的值改变了 " ); 52 53 }54 }
结果如下 我应该在1的前面被调用,我只被调用一次。 用我前,先会查看是否有静态构造方法的定义,如果有先执行它,再得出i=1 ------------- 我是无参构造器!,我给j赋值后,只要继承我,使用:this(),你们的j和s都是这个值 ------------- 我是无参构造器!,我给j赋值后,只要继承我,使用:this(),你们的j和s都是这个值 j=10 s的值改变了
通过上面的实例和注释,对照运行结果,同学们要熟练的运用和理解。
因为我们现在还没有学习到继承,其实在一个类创建成对象的时候,创建时,编译器会先看这个类是否有父类,如果有父类,再继续找是否有父类的父类,我习惯叫爷爷类,如果有爷爷类,再看有没有太爷爷类,如果没有太爷爷类,就会先执行爷爷类的构造方法,再执行爸爸类的构造方法,最后才执行自己的构造方法,当然所有的构造方法的继承都是指实例构造函数,静态的构造函数是无法被继承的。下面的实例就是一个构造方法的继承顺序,现在看不懂没有关系,只是想让大家了解创建对象的工作过程,这里提前讲一个知识点就是继承的语法如A是父类,B是子类,那么在定义B类的时候,语法是这样写的Class B:A,用“:”来表明继承关系,在java中是用“extends ”关键字,现在看看代码,理解一下构造方法的继承 结果:
构造方法在继承中的顺序 class Class1 可以看出,类在继承时,构造函数在实例化的过程中,是会被再次调用的。 { static void Main() { Console.WriteLine( " ********** " ); A a = new A(); Console.WriteLine( " ********** " ); B b = new B(); Console.WriteLine( " ***** AC : A***** " ); AC ac = new AC(); Console.WriteLine( " *****BC : B***** " ); BC bc = new BC(); Console.WriteLine( " *****CC : AC***** " ); CC cc = new CC(); Console.WriteLine( " *****CCC : BC***** " ); CCC ccc = new CCC(); } } class A { public A() { Console.WriteLine( " Call method A() " ); } } class B { public B() { Console.WriteLine( " Call method B() " ); } } class AC : A { public AC() { Console.WriteLine( " Call method AC() " ); } } class BC : B { public BC() { Console.WriteLine( " Call method BC() " ); } } class CC : AC { public CC() { Console.WriteLine( " Call method CC() " ); } } class CCC : BC { public CCC() { Console.WriteLine( " Call method CCC() " ); }
结果如下: ********** Call method A() ********** Call method B() ***** AC : A***** Call method A() Call method AC() *****BC : B***** Call method B() Call method BC() *****CC : AC***** Call method A() Call method AC() Call method CC() *****CCC : BC***** Call method B() Call method BC() Call method CCC() 请按任意键继续. . .
虽然我没有写注释,但是相信同学们都应该能理解这个过程,构造器的继承我也会在类七中再继续深化讲解。构造方法的出现使得我们开发人员可设置默认值、限制实例化以及编写更加灵活且便于阅读的代码。接下来我们来学习本节课最后一个知识点,析构方法。
析构方法
析构方法也叫销毁方法,也有人称为析构器,类借助构造方法进行对象的初始化,借助析构方法进行对象的终止操作,析构方法也是使用类名相同的名称命名,用“~”修饰,当对象被系统终止并且回收时,析构方法执行,我们可以在析构方法中写方法,它会最后被执行,析构方法的语法书写如:~ClassName(){},析构函数是不能有参的,并且一个类中只能有一个析构方法,类中有几个构造方法,析构方法就有被执行几次。我们把上面的实例中加入析构方法,再执行一次代码。
加入析构方法的第一个实例 namespace hello { // 创建一个人类Ren class Ren { // 虽然我把析构方法写在最上面的,但是它是最后执行的方法。 ~ Ren() { Console.WriteLine( " 灭亡了 " ); } /* 观察字段成员如果不赋初值时,在创建对象的时候, * 编译器会按照类型默认的给变量一个初值,如年龄、姓名 * 我们可以在构造方法中,对未赋值的字段进行改变,如年龄 * 如果字段已经赋初值了,那么创建对象的时候, * 就会使用初值,如果需要改变,就在构造方法中重新赋值即可,如性别 */ // 定义一个年龄变量,不赋初值 int age; // 定义一个姓名变量,不赋初值 string name; // 定义一个性别变量,赋初值为男 string Sex = " 男 " ; // 定义一个方法,读取类中字段成员,值的变化。 void Say() { Console.WriteLine( " 我的年龄:{0},我的名字:{1},我的性别:{2} " , age, name,Sex ); } /* 1.以下方法都为Ren这个类的构造方法,他们因为参数不同,形成了方法的重载, * 2.this关键字:this关键字用来引用对象本身,已经是说,this.name指的是对象中的name字段, * 而=name的这个name指的是方法中离它定义最近的name,这里值的是构造方法中传入的name参数 * this关键字在例子结束后,还会具体的讲解。 */ // 在无参的构造方法中,给性别重新赋值,年龄和姓名按照创建对象时默认的赋初值 public Ren() { // 改变性别的值 Sex = " 女 " ; Console.WriteLine( " 我刚出生!还没有名字,年龄也从0岁开始! " ); // 此时读取对象中的字段值的变化,应该性别改变,年龄和姓名都为默认值 Say(); } // 创建一个带姓名参数的构造方法,将创建对象时传入的name参数赋给对象中的字段name值,这样say方法中的姓名也有了改变 // 同时在这个构造方法中,给age字段自定义赋值。 public Ren( string name ) { this . name = name ; Console.WriteLine( " 呦!我使用双截棍,我有名字了!,但是年龄呢?,我自己定,我就27岁了。 " ); age = 27 ; Say(); } // 创建一个带姓名参数、姓名的构造方法,将创建对象时传入的name参数赋给对象中的字段name值, // 将创建对象时传入的age参数的值赋给对象中的字段age,这样say方法中的姓名、年邻都有了改变 public Ren( int age, string name) { this .age = age; this .name = name; Console.WriteLine( " 哦!你给我起名字,告诉我多大了吧!你说的算! " ); Say(); } // 定义静态的构造函数,我把她放在在最后,但是会第一个被调用。 static Ren() { Console.WriteLine( " 我是静态的构造函数,不能有参数及访问修饰符,并且创建对象时,我只执行一次,最先调用 " ); Console.WriteLine(); } } class Program { static void Main( string [] args) { // 创建无参的对象boby,并且创建时会先调用静态的构造方法,再调用无参的构造方法 Ren boby = new Ren(); Console.WriteLine(); // 创建有姓名参数的对象songer歌手 Ren songer = new Ren( " 周杰伦 " ); Console.WriteLine(); // 创建有年龄、姓名参数的对象man Ren man = new Ren( 33 , " 贝克汉姆 " ); Console.WriteLine(); } } }
结果如下:
我是静态的构造函数,不能有参数及访问修饰符,并且创建对象时,我只执行一次,最先调用
我刚出生!还没有名字,年龄也从0岁开始! 我的年龄:0,我的名字:,我的性别:女
呦!我使用双截棍,我有名字了!,但是年龄呢?,我自己定,我就27岁了。 我的年龄:27,我的名字:周杰伦,我的性别:男
哦!你给我起名字,告诉我多大了吧!你说的算! 我的年龄:33,我的名字:贝克汉姆,我的性别:男
灭亡了 灭亡了 灭亡了 请按任意键继续. . .
因为析构方法比较好理解,看这个实例同学们就能明白他的用法,析构函数的继承顺序是按照从派生程度最大到最小的顺序调用的,也就是与构造方法继承顺序相反,先结束子类的析构方法,再结束爸爸类的,再结束爷爷类的,这点也比较好理解,可以把构造方法和析构方法看出一对开始和结束的标记,内部的开始标记最先结束,最外部的开始标记,最后结束。
本节课我们就学到这里,下节课我们来学习类的六大分类。