Chinaunix首页 | 论坛 | 博客
  • 博客访问: 59729
  • 博文数量: 12
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 125
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-06 19:21
文章分类

全部博文(12)

文章存档

2008年(12)

我的朋友

分类:

2008-12-14 21:10:14

  所有的对象都继承自Tobject

如:

Type

  Ttest = class

  End;

Ttest类仍然会被编译器当作Tobject类的子类。因此Ttest自然继承了Tobject的构造方法和析构方法

 

对象的建立——对象的建立是由程序员和编译器共同完成的。

   Delphi中所有的类都至少有一个构造方法,要么是自定义的,要么是从基类继承而来的。但是查看Tobject类的构造方法Create,我们发现这只是一个空方法。既然是空方法,还需要它做什么呢?

1.         为什么需要构造方法?

要解决这个问题就需要知道类的使用方法,如使用上面定义的Ttest类:

Var

  Test1: Ttest;

Begin

  Test1 := Ttest.Create;

End;

假如我们允许Ttest没有构造方法,那么永远没有办法使用Ttest。因为没有办法指定编译器为我们在堆中分配一块内存,以存放Ttest实例的数据。

可见,构造方法的一个重要功能就是和编译器会话:通知编译为我们在堆中建立起对象的内存结构。当然编译器并不能真的为我们在堆中建立对象,因为对象的建立是在程序运行期间进行的。编译器只是在我们调用构造方法的地方自动为我们插入适当的用于建立对象的代码而已。

再理解下面的话:

构造方法是一种可以用在类中、为类的一个实例分配内存的特殊对象方法。对象方法会为类创建一个新实例,并初始化新实例的所有数据。

从这里可以引申出构造方法的另一个重要功能:指导编译器初始化类实例。编译器在建立内存结构时,默认是将实例的所有数据都初始化为0。但是我们可以在构造方法中改变这种默认行为,即可以指定类的成员的初始值。

其实构造方法还有另一个重要功能:调用其它构造方法。如

Ttest2 class

  Test : Ttest;

End;

Ttest2中有一个成员Test。编译器只能为Test引用分配内存,但是却不能为Test所指向的实际对象分配内存。因此,需要我们在构造方法中调用Ttest的构造方法来为Test赋值。

其实这个功能和功能二是一样的,都是初始化问题。如果我们没有在Ttest2.Create中调用Test :=Ttest.Create,就相当于没有为Test进行初始化。

 

2.         需要为自定义类提供构造方法吗?

关于构造方法,许多人的困扰是:我自己定义了一个类,但是不知道要不要为其定义一个构造方法。如果知道了构造方法的功能,那么这个问题将不再是一个问题。如果你的类的所有成员变量都不需要初始化(这里的初始化包括类成员变量的初始化),那么你不需要为该类显式提供一个构造函数,直接使用基类的构造函数即可。

说明:所有的类成员都是需要初始化的。因为类成员其实就是指针,所有的指针在使用之前都要初始化

 

对象的销毁——析构方法

1.         析构方法是不是出问题了

书上说:构造方法用于分配内存,析构方法用于释放对象占用的内存。但是看下面实例:

  TTest = class

  public

    iData: Integer;

  end;

procedure TForm1.Button1Click(Sender: TObject);

var

  test : TTest;

  i: Integer;

begin

  test := TTest.Create;

  test.iData := 100;

  test.Free;

  i := test.iData;   //i的结果一般情况下为100

end;

在上面这段代码中,当Free方法被调用之后test占用的内存就应当被释放了,但是为什么还能够引用test.iData呢?这就需要理解“申请内存”和“释放内存”这两个概念了。

系统的内存由操作系统统一管理,当程序需要使用内存时就会向操作系统申请。操作系统接收到一个内存申请时,就会在内存中划分一块适当大小的区域,然后将个区域的地址返回给程序,这样程序就可以使用这块内存区域了。操作系统在为程序分配一块内存区域之后,就会将这块内存区域锁定。如果以后再有别的程序来申请内存时,它不会把锁定的内存区域分配出去。这意味着,程序申请一块内存之后,这块内存就是该程序所独享的,不受外来干扰。当程序使用完这块内存之后,就要释放这块内存,即让操作系统给这块内存解锁。一块内存被解锁之后,就可以分配给其它的程序了。如果程序只是申请内存,在使用完之后却不释放内存,那么该块内存将永远不能被其它程序使用,直到程序结束。这就是所谓的内存泄漏。

再来分析上面的问题:假设test对象原先占用的内存块为M

虽然test.Free函数调用析构方法释放了M。但是这只是通知操作系统对M进行解锁而已。在没有别的程序使用M之前,这块内存的值仍然不会发生变化。另外test.Free只是释放test所占用的内存,但是却没有重置test的值,即test仍然指向M,因此通过test.iData仍然能够引用。但是这种用法在理论是显然是存在风险的,因为现在的操作系统都是多任务的。也许恰好当test.Free执行完之后,CPU使用权被分配给了别的程序P,而P恰好就申请了内存块M,并且重置了M的值。然后CPU的使用权又回归到当前程序中,那么这个时候i := test.iData的值将是不确定的。

 

2.         需要为自定义类提供析构方法吗?

如果自定义类中存放类成员,则需要提供析构方法,并在析构方法中调用该类实例的析构方法。如果类的所有成员都为简单变量,则无须为其提供析构函数。为什么呢?且看下面的实例:

Ttest3 = class(TObject)

Private

  Test: Ttest;

End;

Test作为一个类引用,其占用的内存包含两个方面:一是其本身作为指针所占用的内存,一是其指向的Ttest实例所占用的内存。如果我们不为Ttest3提供析构函数,并在析构函数中调用test.free的话。那么从Tobject继承而来的Destroy方法实际只是释放了Test作为指针所占用的内存,而其指向的Ttest实例所占用的内存并没有释放,而且将永远得不到释放。内存泄漏又发生了。 

 

什么时候需要调用析构方法

   delphi里所有的类的对象分配全部使用堆分配的方式,而c++里还允许栈分配的方式(MyObject   obj;),使用栈分配的方式时(比如函数中的局部变量)在生存期结束时,会自动调用析构函数进行释放相关资源!但如果是堆分配方式的话(如c++使用   new 关键字或delphi使用create函数),那么如果需要释放实体都必须使用显式声明,(如free   destroy。这也就是说Delphi中程序员创建的所有类对象都需要手动释放。然而事情总是有例外,对于从Tcomponent继承下来的对象,在Create的时候可以指定一个所有者。如果一个对象在创建时指定了所有者,那么该对象的生存期将由所有者进行管理。所有者在析构时,会同时析构它所拥有的全部对象。

阅读(1733) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~