Pascal 类

  • 您已经看到Pascal Objects展示了一些面向对象范例的特征。它们实现封装,数据隐藏和继承,但它们也有局限性。例如,Pascal对象不参与多态。因此,类被广泛用于在程序中实现适当的面向对象的行为,尤其是基于GUI的软件。类的定义与对象的定义几乎相同,但是它是指向对象而不是对象本身的指针。从技术上讲,这意味着该类分配在程序的堆上,而对象分配在堆栈上。换句话说,当您将变量声明为对象类型时,它将在堆栈上占用与对象大小相同的空间,但是当您声明类类型的变量时,它将始终使用指针的大小在堆栈上。实际的类数据将在堆上。
  • 定义 Pascal 类

    使用类型声明以与对象相同的方式声明类。类声明的一般形式如下-
    
    type class-identifier = class  
       private
          field1 : field-type;  
          field2 : field-type;  
            ...
       
       public
          constructor create();
          procedure proc1;  
          function f1(): function-type;
    end;  
    var classvar : class-identifier;
    
    值得注意的是以下要点-
    • 类定义应仅位于程序的类型声明部分下。
    • 使用class关键字定义一个类。
    • 字段是类的每个实例中存在的数据项。
    • 方法在类的定义中声明。
    • Root类中有一个名为Create的预定义构造函数。每个抽象类和每个具体类都是Root的后代,因此所有类都至少具有一个构造函数。
    • Root类中有一个预定义的析构函数,称为Destroy。每个抽象类和每个具体类都是Root的后代,因此,所有类都至少具有一个析构函数。
    让我们定义一个Rectangle类,它具有两个整数类型的数据成员-长度和宽度,以及一些操作这些数据成员的成员函数以及一个绘制矩形的过程。
    
    type
       Rectangle = class
       private
          length, width: integer;
       
       public
          constructor create(l, w: integer);
          procedure setlength(l: integer);
          function getlength(): integer;
          procedure setwidth(w: integer);
          function getwidth(): integer;
          procedure draw;
    end;
    
    让我们编写一个完整的程序,该程序将创建矩形类的实例并绘制矩形。这与我们讨论Pascal对象时使用的示例相同。您会发现两个程序几乎相同,但以下情况除外:
    • 您需要包括{$mode objfpc}指令才能使用这些类。
    • 您将需要包含{$m+}指令以使用构造函数。
    • 类实例化与对象实例化不同。仅声明变量不会为实例创建空间,您将使用构造函数create来分配内存。
    这是完整的示例-
    
    {$mode objfpc} // 用来定义类的指令
    {$m+}      // 用于使用构造函数的指令
    
    program exClass;
    type
       Rectangle = class
       private
          length, width: integer;
       
       public
          constructor create(l, w: integer);
          procedure setlength(l: integer);
          
          function getlength(): integer;
          procedure setwidth(w: integer);
          
          function getwidth(): integer;
          procedure draw;
    end;
    var
       r1: Rectangle;
    
    constructor Rectangle.create(l, w: integer);
    begin
       length := l;
       width := w;
    end;
    
    procedure Rectangle.setlength(l: integer);
    begin
       length := l;
    end;
    
    procedure Rectangle.setwidth(w: integer);
    begin
       width :=w;
    end;
    
    function Rectangle.getlength(): integer;
    begin
       getlength := length;
    end;
    
    function Rectangle.getwidth(): integer;
    begin
       getwidth := width;
    end;
    
    procedure Rectangle.draw;
    var
       i, j: integer;
    begin
       for i:= 1 to length do
       begin
          for j:= 1 to width do
             write(' * ');
          writeln;
       end;
    end;
    
    begin
       r1:= Rectangle.create(3, 7);
       
       writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth());
       r1.draw;
       r1.setlength(4);
       r1.setwidth(6);
       
       writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth());
       r1.draw;
    end.
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    Draw Rectangle: 3 by 7
    * * * * * * *
    * * * * * * *
    * * * * * * *
    Draw Rectangle: 4 by 6
    * * * * * * 
    * * * * * * 
    * * * * * * 
    * * * * * *
    
  • 类成员的可见性

    可见性表示班级成员的可访问性。Pascal类成员具有五种可见性-
    • Public 这些成员始终可以访问。
    • Private 这些成员只能在包含类定义的模块或单元中访问。可以从类方法内部或外部访问它们。
    • Strict Private 这些成员只能从类本身的方法访问。同一单元中的其他类或子孙类无法访问它们。
    • Protected 这与private相同,除了这些成员可用于后代类型,即使它们在其他模块中实现也是如此。
    • Published 这与Public相同,但是如果编译器处于{$ M +}状态,则编译器会生成自动流式传输这些类所需的类型信息。发布部分中定义的字段必须是类类型。
  • Pascal 类的构造函数和析构函数

    构造函数是特殊的方法,每当创建一个对象时,它们都会被自动调用。因此,我们通过构造函数初始化许多事情,从而充分利用了这种行为。Pascal提供了一个名为create()的特殊函数来定义构造函数。您可以根据需要将任意数量的参数传递给构造函数。下面的示例将为名为Books的类创建一个构造函数,并在创建对象时初始化该书的价格和标题。
    
    program classExample;
    
    {$MODE OBJFPC} //directive to be used for creating classes
    {$M+} //directive that allows class constructors and destructors
    type
       Books = Class 
       private 
          title : String; 
          price: real;
       
       public
          constructor Create(t : String; p: real); //default constructor
          
          procedure setTitle(t : String); //sets title for a book
          function getTitle() : String; //retrieves title
          
          procedure setPrice(p : real); //sets price for a book
          function getPrice() : real; //retrieves price
          
          procedure Display(); // display details of a book
    end;
    var
       physics, chemistry, maths: Books;
    
    //default constructor 
    constructor Books.Create(t : String; p: real);
    begin
       title := t;
       price := p;
    end;
    
    procedure Books.setTitle(t : String); //sets title for a book
    begin
       title := t;
    end;
    
    function Books.getTitle() : String; //retrieves title
    begin
       getTitle := title;
    end;
    
    procedure Books.setPrice(p : real); //sets price for a book
    begin
       price := p;
    end;
    
    function Books.getPrice() : real; //retrieves price
    begin
       getPrice:= price;
    end;
    
    procedure Books.Display();
    begin
       writeln('Title: ', title);
       writeln('Price: ', price:5:2);
    end;
    
    begin 
       physics := Books.Create('Physics for High School', 10);
       chemistry := Books.Create('Advanced Chemistry', 15);
       maths := Books.Create('Algebra', 7);
       
       physics.Display;
       chemistry.Display;
       maths.Display;
    end.
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    Title: Physics for High School
    Price: 10
    Title: Advanced Chemistry
    Price: 15
    Title: Algebra
    Price: 7
    
    像名为create的隐式构造函数一样,也有一个隐式析构函数方法destroy,可以使用该方法释放类中使用的所有资源。
  • 继承

    Pascal类定义可以选择从父类定义继承。语法如下-
    
    type
    childClas-identifier = class(baseClass-identifier) 
    < members >
    end; 
    
    以下示例提供了一个Novels类,该类继承了Books类并根据需要添加了更多功能。
    
    program inheritanceExample;
    
    {$MODE OBJFPC} //directive to be used for creating classes
    {$M+} //directive that allows class constructors and destructors
    
    type
       Books = Class 
       protected 
          title : String; 
          price: real;
       
       public
          constructor Create(t : String; p: real); //default constructor
          
          procedure setTitle(t : String); //sets title for a book
          function getTitle() : String; //retrieves title
          
          procedure setPrice(p : real); //sets price for a book
          function getPrice() : real; //retrieves price
          
          procedure Display(); virtual; // display details of a book
    end;
    (* Creating a derived class *)
    
    type
       Novels = Class(Books)
       private
          author: String;
       
       public
          constructor Create(t: String); overload;
          constructor Create(a: String; t: String; p: real); overload;
          
          procedure setAuthor(a: String); // sets author for a book
          function getAuthor(): String; // retrieves author name
          
          procedure Display(); override;
    end;
    var
       n1, n2: Novels;
    
    //default constructor 
    constructor Books.Create(t : String; p: real);
    begin
       title := t;
       price := p;
    end;
    
    procedure Books.setTitle(t : String); //sets title for a book
    begin
       title := t;
    end;
    
    function Books.getTitle() : String; //retrieves title
    begin
       getTitle := title;
    end;
    
    procedure Books.setPrice(p : real); //sets price for a book
    begin
       price := p;
    end;
    
    function Books.getPrice() : real; //retrieves price
    begin
       getPrice:= price;
    end;
    
    procedure Books.Display();
    begin
       writeln('Title: ', title);
       writeln('Price: ', price);
    end;
    
    (* Now the derived class methods  *)
    constructor Novels.Create(t: String);
    begin
       inherited Create(t, 0.0);
       author:= ' ';
    end;
    
    constructor Novels.Create(a: String; t: String; p: real);
    begin
       inherited Create(t, p);
       author:= a;
    end;
    
    procedure Novels.setAuthor(a : String); //sets author for a book
    begin
       author := a;
    end;
    
    function Novels.getAuthor() : String; //retrieves author
    begin
       getAuthor := author;
    end;
    
    procedure Novels.Display();
    begin
       writeln('Title: ', title);
       writeln('Price: ', price:5:2);
       writeln('Author: ', author);
    end;
    
    begin 
       n1 := Novels.Create('Gone with the Wind');
       n2 := Novels.Create('Ayn Rand','Atlas Shrugged', 467.75);
       n1.setAuthor('Margaret Mitchell');
       n1.setPrice(375.99);
       n1.Display;
       n2.Display;
    end.
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    Title: Gone with the Wind
    Price: 375.99
    Author: Margaret Mitchell
    Title: Atlas Shrugged
    Price: 467.75
    Author: Ayn Rand
    
    值得注意的是以下要点-
    • Books类的成员具有受保护的(Protected)可见性。
    • Novels类具有两个构造函数,因此重载运算符用于函数重载。
    • Books.Display过程已声明为virtual,因此Novels类中的相同方法可以覆盖它。
    • Novels.Create构造函数调用使用基类的构造继承关键字。
  • 接口

    定义接口以为实现者提供通用功能名称。不同的实现者可以根据他们的要求实现这些接口。可以说,接口是框架,由开发人员实现。以下是接口的示例-
    
    type  
       Mail = Interface  
          Procedure SendMail;  
          Procedure GetMail;  
       end;  
       
       Report = Class(TInterfacedObject,  Mail)  
          Procedure SendMail;  
          Procedure GetMail;  
       end;  
    
    请注意,当一个类实现一个接口时,它应该实现该接口的所有方法。如果未实现接口方法,则编译器将给出错误。
  • 抽象类

    抽象类是无法实例化的类,只能继承。通过在类定义中包括符号抽象来指定抽象类,如下所示-
    
    type
       Shape = ABSTRACT CLASS (Root)
          Procedure draw; ABSTRACT;
          ...
       end;
    
    从抽象类继承时,父类声明中标记为抽象的所有方法必须由子代定义;此外,必须以相同的可见性定义这些方法。
  • static关键字

    将类成员或方法声明为静态使其可以访问,而无需实例化该类。实例化的类对象无法访问声明为static的成员(尽管static方法可以)。以下示例说明了概念-
    
    program StaticExample;
    {$mode objfpc}
    {$static on}
    type
       myclass=class
          num : integer;static;
       end;
    var
       n1, n2 : myclass;
    begin
       n1:= myclass.create;
       n2:= myclass.create;
       n1.num := 12;
       writeln(n2.num);
       n2.num := 31;
       writeln(n1.num);
       writeln(myclass.num);
       myclass.num := myclass.num + 20;
       writeln(n1.num);
       writeln(n2.num);
    end.
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    12
    31
    31
    51
    51
    
    您必须使用伪指令{$static on}来使用静态成员。