delphi高手突破学习笔记之面向对象类和对象的本质(有汇编解释 good)

时间:2023-03-09 09:33:18
delphi高手突破学习笔记之面向对象类和对象的本质(有汇编解释 good)

知识点1:堆和栈

每个应用程序可以获得的内存空间分为两种:堆(heap)和栈(stack)。

堆又称为“*存储区”,其中的内存空间的分配与释放是必须由程序员来控制的。例如,用GetMem函数获取了一定大小的内存空间,则在使用完后,必须调用FreeMem函数将空间释放,否则就会发生所谓的“内存泄漏”。“借债还钱,天经地义”。

栈又称为“自动存储区”,其中的内存空间的分配与释放是由编译器和系统自动完成的,不需要程序员过问。函数调用时按值传递的参数所占空间、函数中的局部变量等,都是在栈中被分配空间的。比如函数:

  1. var
  2. i : Integer;
  3. j : Integer;
  4. begin
  5. for i := 1 to 10 do
  6. ......
  7. ......
  8. end;

其中,i和j的空间是由编译器在栈中分配的。在函数末尾,也不需要程序员手动去释放这两个变量所占的内存空间。

Objecgt Pascal遵循所谓的“引用/值”模型。无论在参数传递还是变量定义中,简单类型(如Integer、Cardinal、char以及record等)被按值传递或使用,其内存空间从栈中分配。

而复杂类型(class)则被按引用传递或使用,其内存空间从堆中分配。在Object Pascal中,所有对象(类类型的)都被建立在内存的堆空间上,而非栈上。因此在创建对象时,其构造函数不会被编译器自动调用,也没有C++中所谓的“默认构造函数”。调用构造函数来创建对象以及调用析构函数来消灭对象都是程序员的职责。

知识点2:构造函数与对象内存的分配

定义构造函数使用Constructor关键字。按惯例,构造函数名称为Create(当然也可以用其他名称,但那绝非优良的设计)。要创建出一个对象,首先需要分配对象本身所占用的内存空间,然后执行类的构造函数,以初始化各数据成员、申请对象需要的资源或创建其内部包含的子对象。编译器在执行类似MyFamilyObject := TMyFamily.Create(‘Zhang’, ‘Li’);这样的构造函数之前,会插入以下几行汇编代码:

  1. test dl, dl
  2. jz +$08
  3. add esp, -$10
  4. call @ClassCreate // 注意这行代码

以上代码的最后一行代码调用的是system.pas文件的第8949行的_ClassCreate函数。内存分配完成后是调用类的构造函数,即TMyClass.Create(),以初始化数据成员。构造函数由定义类的程序员编写,也就是说,将对象初始化成何种模样是由程序员决定的。至此,一个对象已经诞生了。之后,编译器会再插入以下几行汇编代码:

  1. test dl, dl
  2. jz +$0f
  3. call @AfterConstruction
  4. pop dword ptr fs:[$00000000]
  5. add esp, $0c

构造函数的职责只是初始化对象的数据成员,没有构造函数只意味着不会对数据成员进行初始化而已,编译器会对所有数据进行清零初始化。

知识点3:析构函数与对象内存的回收

定义析构函数使用Destructor关键字。按惯例,析构函数名称为Destroy。

  1. type
  2. TMyClass = class
  3. Public
  4. Destructor Destroy(); override;
  5. End;

在析构对象的时候,应该调用对象的Free()方法而不是直接调用Destroy()。

这是因为,在TObject的Free()方法中会判断对象本身是否为nil,如果不为nil则调用对象的Destroy(),以增加安全性。既然有这样更安全的做法,当然没有理由不这么做了。

永远不要直接调用对象的Destroy(),而应该是Free()。
知识点4:实例介绍

  1. unit DllLoader;
  2. interface
  3. uses windows;
  4. Type
  5. TDllLoader = class
  6. Protected
  7. // 之所以是protected成员,是为了在其派生类中具体实现加载某DLL时,派生类 能够访问该句柄
  8. FhDLL : HMODULE;
  9. Public
  10. Constructor Create(strDLLName : String);
  11. Destroctor Destroy(); override;
  12. End;
  13. Implementation
  14. Constructor TDllLoader.Create(strDLLName : String);
  15. Begin
  16. FhDLL := LoadLibrary(strDLLName); // 构造函数中加载DLL
  17. ASSERT(FhDLL <> 0);
  18. End;
  19. Destructor TDllLoader.Destroy();
  20. Begin
  21. If FhDLL <> 0 then
  22. begin
  23. FreeLibrary(FhDLL); // 析构函数中释放DLL
  24. FhDLL := 0;
  25. End;
  26. End;
  27. End.

知识点5:“类方法”与“类引用”类型

一般所称的“方法”,都是指“对象方法”。也就是说,执行该方法,将可能导致对象的状态发生改变,即该方法可以更改对象的数据成员的值。如:

  1. TMyClass2 = class
  2. private
  3. FMember : Integer;
  4. pubic
  5. procedure SetValue(Value : Integer);
  6. end;
  7. procedure TMyClass2.SetValue(Value : Integer);
  8. begin
  9. FMember := Value;
  10. end;

其中SetValue即为典型的对象方法。
除了“对象方法”外,还有所谓的“类方法”,也就是属于类级别的函数(而非对象级别的)。它可以改变类的状态(而非对象的状态)。
定义类方法,只需要在一般的方法声明前加上class关键字。如:

  1. class function TObject.ClassName: ShortString;

既然类方法是进行类级别的操作,因此在类方法中是无法对对象的数据成员进行访问的。以下的代码展示了类方法中self的使用方法:

  1. interface
  2. Type
  3. TClassMethodExample = class
    1. private
    2. FnInteger : Integer;
    3. public
    4. class function ClassMethod1() : Integer;
    5. class function ClassMethod2() : Integer;
    6. function Method() : Integer;
    7. end;
    8. implementation
    9. { TClassMethodExample }
    10. class function TClassMethodExample.ClassMethod1: Integer;
    11. begin
    12. self.Method() ; // 非法,因为Method不是类方法
    13. self.ClassMethod2(); // 合法
    14. end;
    15. class function TClassMethodExample.ClassMethod2: Integer;
    16. begin
    17. Result := self.nInteger; // 非法,类方法中不能访问对象数据成员
    18. end;
    19. function TClassMethodExample.Method: Integer;
    20. begin
    21. Result := 0;
    22. end;
    23. end.

http://blog.****.net/sushengmiyan/article/details/7506310