Delphi 基础知识:类和对象

目录

一、类的类型

二、继承性和范围

1. TObject and TClass

2.  类类型的兼容性

3. 对象类型

三、类成员的可见性

1. Private, Protected, and Public Members

2. 严格的可见性规定者

3. Published Members

4. Automated Members (Win32 Only)

扫描二维码关注公众号,回复: 15248581 查看本文章

四、前向声明和相互依赖的类


本片文章我们将讨论如下议题:

  • 类的声明语法
  • 继承和范围
  • 类成员的可见性
  • 前向声明和相互依赖的类
     

一、类的类型

一个类,或类的类型,定义了一个由字段、方法和属性组成的结构。一个类类型的实例被称为对象。一个类的字段、方法和属性被称为其组成部分或成员。

一个字段本质上是一个变量,是一个对象的一部分。像记录的字段一样,类的字段代表存在于类的每个实例中的数据项。
方法是一个与类相关的程序或函数。大多数方法对对象进行操作,也就是一个类的实例进行操作。有些方法(称为类方法)对类类型本身进行操作。
属性是与一个对象相关的数据的接口(通常存储在一个字段中)。属性有访问指定符,它决定了它们的数据如何被读取和修改。从对象本身之外的程序的其他部分来看,属性在大多数方面看起来就像一个字段。
对象是动态分配的内存块,其结构由其类别类型决定。每个对象对类中定义的每个字段都有一个唯一的副本,但一个类的所有实例都共享相同的方法。对象是由被称为构造函数和析构函数的特殊方法创建和销毁的。

一个类类型的变量实际上是一个引用对象的指针。因此,一个以上的变量可以指代同一个对象。像其他指针一样,类类型的变量可以持有nil值。但是你不必明确地解除对一个类型变量的引用来访问它所指向的对象。例如,SomeObject.Size := 100为SomeObject所引用的对象的Size属性赋值100;你不会把它写成SomeObject^.Size := 100。

一个类的类型在被实例化之前必须被声明并给它一个名字。(你不能在变量声明中定义一个类的类型。)只在程序或单元的最外层范围内声明类,而不是在过程或函数声明中。

一个类的类型声明有以下形式:

type
    className = class [abstract | sealed] (ancestorClass)
        type
          nestedTypeDeclaration
        const
	  nestedConstDeclaration
        memberList
    end;

类类型声明的必要元素:

  1. className 是任何有效的标识符。
  2. memberList 声明了该类的成员:字段、方法和属性。

类类型声明的可选元素:

  1. abstract(抽象的):整个类可以被声明为抽象的,即使它不包含任何抽象的虚拟方法。
  2. sealed(密封的):一个密封的类不能通过继承来扩展。
  3. ancestorClass(继承的):如果你省略了(ancestorClass),新类直接继承于预定义的System.TObject类。如果你包含了(anjestorClass),并且memberList是空的,你可以省略结束。
  4. nestedTypeDeclaration(嵌套类型声明):嵌套类型提供了一种将概念上相关的类型放在一起并避免名称冲突的方法。
  5. nestedConstDeclaration:嵌套的常量提供了一种方法来保持概念上相关的类型在一起,并避免名称的碰撞。

一个类不能既是abstract(抽象的)又是sealed(密封的)。[abstract | sealed] 语法([ ]括号和它们之间的 | 管子)用于指定只能使用可选的 sealed 或 abstract 关键字中的一个。只有密封的或抽象的关键字才是有意义的。大括号和管道符号应该被删除。

注意:

        为了向后兼容,Delphi允许实例化一个声明为抽象的类,但这个功能不应该再被使用。

方法在类的声明中作为函数或过程的标题出现,没有主体。每个方法的定义声明出现在程序的其他地方。

例如,这里是Classes单元中TMemoryStream类的声明:

TMemoryStream = class(TCustomMemoryStream)
  private
    FCapacity: Longint;
    procedure SetCapacity(NewCapacity: Longint);
  protected
    function Realloc(var NewCapacity: Longint): Pointer; virtual;
    property Capacity: Longint read FCapacity write SetCapacity;
  public
    destructor Destroy; override;
    procedure Clear;
    procedure LoadFromStream(Stream: TStream);
    procedure LoadFromFile(const FileName: string);
    procedure SetSize(const NewSize: Int64); override;
    procedure SetSize(NewSize: Longint); override;
    function Write(const Buffer; Count: Longint): Longint; override;
    function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override;
  end; // deprecated 'Use TBytesStream';

类TMemoryStream是类TCustomMemoryStream的后代,继承了它的大部分成员。但是它定义了--或重新定义了--几个方法和属性,包括它的析构器方法Destroy。它的构造函数Create没有改变地继承自System.TObject,所以没有重新声明。每个成员都被声明为私有的、受保护的或公共的(这个类没有published的成员)。下面将解释这些术语。

考虑到这个声明,你可以按如下方式创建TMemoryStream的实例。

var
  stream: TMemoryStream;

begin
  stream := TMemoryStream.Create;

二、继承性和范围

当你声明一个类时,你可以指定它的直接祖先。比如说:

type TSomeControl = class(TControl);

声明了一个名为TSomeControl的类,它是Vcl.Controls.TControl的后代。一个类的类型会自动继承其直系祖先的所有成员。每个类都可以声明新的成员,也可以重新定义继承的成员,但一个类不能删除在祖先中定义的成员。因此,TSomeControl包含了Vcl.Controls.TControl和Vcl.Controls.TControl的每个祖先中定义的所有成员。

一个成员的标识符的作用范围从该成员被声明的地方开始,一直到类的声明结束,并扩展到该类的所有后裔以及该类及其后裔中定义的所有方法块。

1. TObject and TClass

System.TObject类,在System单元中声明,是所有其他类的最终祖先。System.TObject只定义了少量的方法,包括一个基本的构造函数和析构函数。除了System.TObject之外,System单元还声明了类的引用类型System.TClass。

  TClass = class of TObject;

如果一个类类型的声明没有指定一个祖先,那么这个类就直接继承自System.TObject。因此:

type 
     TMyClass = class
      ...
     end;

相当于下面

type 
     TMyClass = class(TObject)
      ...
     end;

为了便于阅读,建议采用后一种形式。

2.  类类型的兼容性

一个类的类型与它的祖先是赋值兼容的。因此,一个类类型的变量可以引用任何后代类型的实例。例如,给定的声明。

type
  TFigure = class(TObject);
  TRectangle = class(TFigure);
  TSquare = class(TRectangle);
var
 Fig: TFigure;

变量Fig可以被分配为TFigure、TRectangle和TSquare类型的值。

3. 对象类型

Delphi编译器允许用另一种语法来代替类类型。你可以使用以下语法来声明对象类型。

type 
     objectTypeName = object (ancestorObjectType)
        memberList
     end;

其中objectTypeName是任何有效的标识符,(祖先ObjectType)是可选的,memberList声明字段、方法和属性。如果(祖先ObjectType)被省略,那么新类型没有祖先。对象类型不能有Published的成员。

由于对象类型不是从System.TObject降生的,它们没有提供内置的构造函数、析构函数或其他方法。你可以用New过程创建一个对象类型的实例,用Dispose过程销毁它们,或者你可以简单地声明一个对象类型的变量,就像你用记录一样。

支持对象类型只是为了向后兼容。不建议使用它们。

三、类成员的可见性

一个类的每个成员都有一个叫做可见性的属性,它由private、protected、public、published或automated中的一个保留字表示。比如:

published 
   property Color: TColor read GetColor write SetColor;

声明了一个名为Color的published属性。可见性决定了一个成员在哪里以及如何被访问,private(私有的)代表最少的可访问性,protected(保护的)代表中间级别的可访问性,而public, published(公共的、公布)的和automated(自动)的代表最大的可访问性。

如果一个成员的声明中没有出现它自己的可见性指定符,那么这个成员的可见性与它前面的成员相同。在类声明的开头没有指定可见性的成员默认为公开的,只要该类是在{$M+}状态下编译的,或者是从{$M+}状态下编译的类派生的;否则,这些成员是公开的。

为了便于阅读,最好按可见性来组织类的声明,把所有的私有成员放在一起,然后是所有的受保护成员,以此类推。这样,每个可见性保留词最多出现一次,并标志着声明中一个新 "部分 "的开始。所以一个典型的类声明应该是这样的。

type
  TMyClass = class(TControl)
    private
      { private declarations here }
    protected
      { protected declarations here }
    public
      { public declarations here }
    published
      { published declarations here }
  end;

你可以通过重新声明来增加一个属性在子类中的可见性,但你不能减少它的可见性。例如,一个受保护的属性可以在子类中被公开,但不能被私有。此外,published的属性不能在子类中变成公开的。更多信息,请参见属性覆盖和重新声明。

1. Private, Protected, and Public Members

一个私有成员在声明该类的单元或程序之外是不可见的。换句话说,一个私有方法不能从另一个模块中调用,一个私有字段或属性不能从另一个模块中读或写。通过将相关的类声明放在同一个模块中,你可以让每个类访问另一个类的私有成员,而不使这些成员被更广泛地访问。如果一个成员只在其类内部可见,它需要被严格声明为私有。

一个被保护的成员在其类被声明的模块中的任何地方都是可见的,也可以从任何子类中看到,不管子类出现在哪个模块。受保护的方法可以被调用,受保护的字段或属性可以被读写,可以从属于声明受保护成员的类的任何方法的定义中调用。那些只用于实现派生类的成员通常是被保护的。

公有成员在其类可以被引用的地方是可见的。

2. 严格的可见性规定者

除了私有和受保护的可见性指定器之外,Delphi编译器还支持具有更大访问限制的额外可见性设置。这些设置是严格私有和严格保护的可见性。

具有严格私有可见性的类成员只能在它们被声明的类中访问。它们对于在同一单元中声明的程序或函数是不可见的。具有严格保护可见性的类成员在它们被声明的类中是可见的,在任何子类中也是可见的,不管它在哪里被声明。此外,当实例成员(那些没有使用class或class var关键字的声明)被声明为严格私有或严格保护时,它们在它们出现的类的实例之外是不可访问的。一个类的实例不能访问同一个类的其他实例中的严格私有或严格保护的实例成员。

注意:

在类声明的范围内,strict这个词被当作指令。在类声明中,你不能声明一个名为 "strict "的成员,但在类声明之外使用它是可以接受的。

3. Published Members

Published(发布的)成员与公共成员具有相同的可见性。不同的是,运行时类型信息(RTTI)是为Published(公布的)成员生成的。RTTI允许应用程序动态地查询一个对象的字段和属性,并定位其方法。在保存和加载表单文件时,RTTI被用来访问属性的值,在对象检查器中显示属性,并将特定的方法(称为事件处理程序)与特定的属性(称为事件)联系起来。

Published(发布的)属性被限制为某些数据类型。序数、字符串、类、接口、变体和方法指针类型可以被发布。集合类型也可以,只要基本类型的上界和下界的序数值从0到31。(换句话说,集合必须适合于一个字节、字或双字。)除了Real48,任何实数类型都可以被发布。数组类型的属性(与下面讨论的数组属性不同)不能被Published。

有些属性虽然可以Published,但并不被流媒体系统完全支持。这些包括记录类型的属性、所有可发布类型的数组属性,以及包括匿名值的枚举类型的属性。如果你Published了这种类型的属性,对象检查器将不会正确显示它,当对象被流式传输到磁盘时,该属性的值也不会被保留下来。

所有的方法都是可以Published(发布的),但是一个类不能Published(发布)两个或多个同名的重载方法。字段只有在属于类或接口类型时才能被Published。

一个类不能有已发布的成员,除非它是在{$M+}状态下编译的,或者是从{$M+}状态下编译的类派生而来。大多数具有发布成员的类都源于Classes.TPersistent,它是在{$M+}状态下编译的,所以很少有必要使用$M指令。
 

注意:含有Unicode字符的标识符不允许出现在类的Published章节中,也不允许出现在已发布的成员所使用的类型中。

4. Automated Members (Win32 Only)

自动化成员与公共成员具有相同的可见性。不同的是,自动化类型信息(自动化服务器需要)是为自动化成员生成的。自动成员通常只出现在Win32类中。为了向后兼容,自动化保留字被保留下来。ComObj单元中的TAutoObject类不使用自动化。

以下限制适用于声明为自动化的方法和属性。

  1. 所有属性、数组属性参数、方法参数和函数结果的类型必须是可自动的。可自动处理的类型有:Byte, Currency, Real, Double, Longint, Integer, Single, Smallint, AnsiString, WideString, TDateTime, Variant, OleVariant, WordBool, 和所有接口类型。
  2. 方法声明必须使用默认的register(寄存器)调用约定。它们可以是虚拟的virtual,但不是动态的dynamic。
  3. 属性声明可以包括访问指定符(读和写),但其他指定符(索引、存储、默认和节点默认)是不允许的。访问指定符必须列出一个使用默认寄存器调用惯例的方法标识符;不允许使用字段标识符。
  4. 属性声明必须指定一个类型。不允许重写属性。

自动化方法或属性的声明可以包括一个dispid指令。在dispid指令中指定一个已经使用过的ID会导致一个错误。

在Win32平台上,这条指令后面必须有一个整数常数,为成员指定一个自动化调度ID。否则,编译器会自动给成员分配一个调度ID,这个调度ID比该类及其祖先的任何方法或属性所使用的最大调度ID大一个。关于自动化的更多信息(仅在Win32上),请参阅自动化对象。

四、前向声明和相互依赖的类

如果一个类类型的声明以class这个词和一个分号结束,也就是说,如果它的形式是

type  
  className = class;

词后没有列出祖先或类成员,那么它就是一个前向声明。一个前向声明必须由同一类型声明部分中的同一类别的定义声明来解决。换句话说,在一个前向声明和它的定义声明之间,除了其他类型声明之外,不能有任何东西发生。

前向声明允许相互依赖的类。比如说 :

type
  TFigure = class;   // forward declaration
  TDrawing = class
    Figure: TFigure;
    // ...
  end;
 
  TFigure = class   // defining declaration
    Drawing: TDrawing;
    // ...
  end;

不要将前向声明与完整的类型声明相混淆,这些类型源自System.TObject而没有声明任何类成员。

type
  TFirstClass = class;   // this is a forward declaration
  TSecondClass = class   // this is a complete class declaration
  end;                   //
  TThirdClass = class(TObject);  // this is a complete class declaration

猜你喜欢

转载自blog.csdn.net/sensor_WU/article/details/129647409
今日推荐