转自站长资讯:
摘 要:结构函数与析构函数是一个类中看似较为简略的两类函数,但在实际应用过程中总会呈现一些意想不到的运行错误。本文将较系统的先容结构函数与析构函数的原理及在C#中的应用,以及在应用过程中需要留心的若干事项。
要害字:结构函数;析构函数;垃圾回收器;非托管资源;托管资源
一.结构函数与析构函数的原理
作为比C更先进的语言,C#供给了更好的机制来加强程序的安全性。C#编译用具有严格的类型安全检查功效,它几乎能找出程序中所有的语法标题,这的确帮了程序员的大忙。但是程序通过了编译检查并不表现错误已经不存在了,在“错误”的大家庭里,“语法错误”的地位只能算是冰山一角。级别高的错误通常暗躲得很深,不轻易发明。
根据经验,不少难以察觉的程序错误是由于变量没有被准确初始化或清除造成的,而初始化和清除工作很轻易被人遗忘。微软利用面向对象的概念在设计C#语言时充分考虑了这个标题并很好地予以解决:把对象的初始化工作放在结构函数中,把清除工作放在析构函数中。当对象被创立时,结构函数被主动履行。当对象灭亡时,析构函数被主动履行。这样就不用担心忘记对象的初始化和清除工作。
二.结构函数在C#中的应用
结构函数的名字不能随便起,必需让编译器认得出才可以被主动履行。它的命名方法既简略又公平:让结构函数与类同名。除了名字外,结构函数的另一个特别之处是没有返回值类型,这与返回值类型为void的函数不同。假如它有返回值类型,那么编译器将不知所措。在你可以拜访一个类的方法、属性或任何其它东西之前, 第一条履行的语句是包含有相应类的结构函数。甚至你自己不写一个结构函数,也会有一个缺省结构函数供给应你。
class TestClass
{
public TestClass(): base() {} // 由CLR供给
}
下面列举了几种类型的结构函数
1)缺省结构函数
class TestClass
{
public TestClass(): base() {}
}
上面已先容,它由系统(CLR)供给。
2)实例结构函数
实例结构函数是实现对类中实例进行初始化的方法成员。如:
using System;
class Point
{
public double x, y;
public Point()
{
this.x = 0;
this.y = 0;
}
public Point(double x, double y)
{
this.x = x;
this.y = y;
}
…
}
class Test
{
static void Main()
{
Point a = new Point();
Point b = new Point(3, 4); // 用结构函数初始化对象
…
}
}
声明了一个类Point,它供给了两个结构函数。它们是重载的。一个是没有参数的Point结构函数和一个是有两个double参数的Point结构函数。假如类中没有供给这些结构函数,那么会CLR会主动供给一个缺省结构函数的。但一旦类中供给了自定义的结构函数,如Point()和Point(double x, double y),则缺省结构函数将不会被供给,这一点要留心。
3) 静态结构函数
静态结构函数是实现对一个类进行初始化的方法成员。它一般用于对静态数据的初始化。静态结构函数不能有参数,不能有润饰符而且不能被调用,当类被加载时,类的静态结构函数主动被调用。如:
using System.Data;
class Employee
{
private static DataSet ds;
static Employee()
{
ds = new DataSet(...);
}
...
}
声明了一个有静态结构函数的类Employee。留心静态结构函数只能对静态数据成员进行初始化,而不能对非静态数据成员进行初始化。但是,非静态结构函数既可以对静态数据成员赋值,也可以对非静态数据成员进行初始化。
假如类仅包含静态成员,你可以创立一个private的结构函数:private TestClass() {…},但是private意味着从类的外面不可能拜访该结构函数。所以,它不能被调用,且没有对象可以被该类定义实例化。
以上是几种类型结构函数的简略应用,下面将重点先容一下在类的层次结构中(即持续结构中)基类和派生类的结构函数的应用方法。派生类对象的初始化由基类和派生类共同完成:基类的成员由基类的结构函数初始化,派生类的成员由派生类的结构函数初始化。
当创立派生类的对象时,系统将会调用基类的结构函数和派生类的结构函数,构 造函数的履行次序是:先履行基类的结构函数,再履行派生类的结构函数。假如派生类又有对象成员,则,先履行基类的结构函数,再履行成员对象类的结构函数,最后履行派生类的结构函数。
至于履行基类的什么结构函数,缺省情况下是履行基类的无参结构函数,假如要履行基类的有参结构函数,则必需在派生类结构函数的成员初始化表中指出。如:
class A
{ private int x;
public A( ) { x = 0; }
public A( int i ) { x = i; }
};
class B : A
{ private int y;
public B( ) { y = 0; }
public B( int i ) { y = i; }
public B( int i, int j ):A(i) { y = j; }
};
B b1 = new B(); //履行基类A的结构函数A(),再履行派生类的结构函数B()
B b2 = new B(1); //履行基类A的结构函数A(),再履行派生类的结构函数B(int)
B b3 = new B(0,1); //履行履行基类A的结构函数A(int) ,再履行派生类的
结构函数B(int,int)
在这里结构函数的履行次序是必定要分析明白的。另外,假如基类A中没有供给无参结构函数public A( ) { x = 0; },则在派生类的所有结构函数成员初始化表中必需指出基类A的有参结构函数A(i),如下所示:
class A
{ private int x;
public A( int i ) { x = i; }
};
class B : A
{ private int y;
public B():A(i) { y = 0; }
public B(int i):A(i) { y = i; }
public B(int i, int j):A(i) { y = j; }
};
三.析构函数和垃圾回收器在C#中的应用
析构函数是实现烧毁一个类的实例的方法成员。析构函数不能有参数,不能任何润饰符而且不能被调用。由于析构函数的目标与结构函数的相反,就加前缀‘~’以示差别。
固然C#(更确实的说是CLR)供给了一种新的内存治理机制---主动内存治理机制(Automatic memory management),资源的开释是可以通过“垃圾回收器” 主动完成的,一般不需要用户干涉,但在有些特别情况下还是需要用到析构函数的,如在C#中非托管资源的开释。
资源的开释一般是通过'垃圾回收器'主动完成的,但具体来说,仍有些需要留心的处所:
1. 值类型和引用类型的引用实在是不需要什么'垃圾回收器'来开释内存的,由于当它们出了作用域后会主动开释所占内存,由于它们都保留在栈(Stack)中;
2. 只有引用类型的引用所指向的对象实例才保留在堆(Heap)中,而堆由于是一个自由存储空间,所以它并没有像'栈'那样有生存期('栈'的元素弹出后就代表生存期结束,也就代表开释了内存),并且要留心的是,'垃圾回收器'只对这块区域起作用;
然而,有些情况下,当需要开释非托管资源时,就必需通过写代码的方法来解决。通常是应用析构函数开释非托管资源,将用户自己编写的开释非托管资源的代码段放在析构函数中即可。需要留心的是,假如一个类中没有应用到非托管资源,那么必定不要定义析构函数,这是由于对象履行了析构函数,那么'垃圾回收器'在开释托管资源之前要先调用析构函数,然后第二次才真正开释托管资源,这样一来,两次删除动作的花销比一次大多的。下面应用一段代码来示析构函数是如何应用的:
public class ResourceHolder
{
…
~ResourceHolder()
{
// 这里是清算非托管资源的用户代码段
}
}
四.小结
结构函数与析构函数固然是一个类中情势上较简略的函数,但它们的应用决非看上往那么简略,因此机动而准确的应用结构函数与析构函数能够帮你更好的懂得CLR的内存治理机制,以及更好的治理系统中的资源。