声明在一个类中的所有“内容”,都被称之为类成员。除了已经学习过的字段和方法,还有:
从语法的角度,当运行new Student()生成对象的时候,实际上是调用了Student类中的构造函数(constructor) .....
等等,这玩意儿在哪里呢?我在Student类里看不到啊!
Good question!带着脑子听课,才是正确的姿势。Good question!带着脑子听课,才是正确的姿势。
如果一个类没有显式的声明任何构造函数,默认就自带一个无参的无内容的构造函数。所以你看不到,但是飞哥可以把它写出来给你一眼:
class Student { public Student() //显式(explicit)的声明 { Console.WriteLine("源栈欢迎你"); }
可以看出,构造函数是在类中:
@想一想@:为什么构造函数没有返回?
如果方法没有返回值,还要给个void,构造函数是咋回事?
其实构造函数是有返回值的,它必然返回当前类的一个实例(对象)!这是不可更改的铁律,所以,在语法设计的时候就干脆省略之,既节省了代码输入,又可以和方法进行区分,赞一个!^_^
构造函数默认无参,但也可以有参数。有可以有内容,我们加上试试:
public Student(string sname) //有参构造函数 { Console.WriteLine("源栈欢迎你," + sname); }
构造函数有参数了,调用的时候就得给参数:
Student atai = new Student("阿泰");
给构造函数传参干嘛呢?
假设我们类中有一个字段name:
private string name;
注意它的private修饰符,@想一想@:它都private了,怎么给它赋值呢?
利用构造函数赋值就是一种常用的方式。构造函数通常用于给字段赋值,就像这样:
public Student(string sname) { name = sname; }
理解: name是当前对象的name
public void greet() { Console.WriteLine("hello, my name is" + name); }
更多的时候我们不会额外的修改构造函数的参数名,以避免和字段名重复:
public Student(string name) { this.name = name; //添加this区分字段name和参数name }
这里因为构造函数的参数name和字段name重名了,所以我们需要在字段name前面显式的加上一个this,告诉编译器:这是当前对象的name.
在类中使用实例成员,实际上都隐式的自带了一个this,除非这种出现混淆的情景,一般都可以省略。
注意: this是只读(readonly)的、不能赋值的。不要混淆this.name—和this
@想一想@:为什么不直接在字段声B时直接值呢?
ES6中的构造函数:(详见)
class Student{ constructor(name){ this.name = name; } } let atai = new Student('atai');
@想一想@:之前private的字段,比如age,如何在外部获取呢?
可以通过方法实现:
public int getAge() { return _age; //返回字段_age的值 }
甚至我们还可以通过方法给private的字段赋值:
public void setAge(int age) { this.age = age; //最终还是利用字段存储 }
这种专门封装字段的方法被称之为属性(或者getter和setter)。
因为封装字段如此常用,以致于形成了一种惯例:类的字段都应该是私有的,必须进行封装。
甚至我们会把属性当成是一种数据(本质上是一种方法),说:
单纯的封装看起来确实有些多余(所以C#等语言引入了一些更简洁的“语法糖”),但还可以在方法体中实现任何我们想要实现的逻辑。比如,对字段的读写进行更复杂的控制:
public void setAge(int age) { //对传入的age值进行验证 if (age < 0 || age > 100) { Console.WriteLine("给age的赋值超过了"); return; } this.age = age; //最终还是利用字段存储 }
public string getName() { return name + "(源栈)"; }
属性是对字段的封装,请再一次仔细体会封装:
你也可以
和构造函数在对象的创建时被调用相反,析构函数在对象被销毁时调用:
~Student() //Java中是finalize() { Console.WriteLine("对象被垃圾回收"); }
和默认的无参构造函数一样,析构函数也默认自带,不需要显式声明。
同时,在99.9999999%的情况下,我们都不需要声明它或者在它的内部添加任何逻辑,此处仅为演示使用。
析构函数在.NET运行时在垃圾回收(garbage collection)时自动调用。
所谓垃圾回收,更易懂的说法是:不再使用的内存回收。所谓回收,就是清除内存上存放的数据,以便再次被使用。
通过前面的学习我们已经知道:变量是有作用域的,变量会在离开作用域(出栈)时被清空,其所占用的内存空间自然就被回收;但是变量所指向的(堆里的)对象呢?
@想一想@:能否在清空一个变量的时候,就销毁它引用/指向的对象?
如果没有自动的垃圾回收机制,就需要开发人员手工的书写代码清除——这无疑是一个非常无聊而且容易被遗忘的事情!一旦遗忘,就很容易造成程序只是不断的新建对象占用内存而没有释放,这就是曾经让开发人员非常头痛的、大名鼎鼎的内存泄漏(memory leak)。
所以,C#、Java、甚至Javascript这些现代编程语言,都提供了自动的垃圾回收功能。简单的说,以C#为例,.NET运行时会在程序运行时自动的检查堆中的对象,是否还在被使用(甚至判断它以后是否还有可能被使用),对已经确定没有被使用,也不太可能再被使用的对象就自动的清除掉。
垃圾清理的算法和运行过程,都是自动的,强烈不建议人为干预。
但为了演示,我们使用了GC强制进行垃圾回收
static void gcShow() //就为了新建一个Student对象 { new Student("曾俊清"); }
Main()函数中调用:
gcShow(); //方法调用完成,方法中的对象不会再被引用 GC.Collect(); //强制垃圾回收,否则因为可用内存足够大,不一定会被回收
多快好省!前端后端,线上线下,名师精讲
更多了解 加: