Pascal - 类



您已经看到 Pascal 对象展现了面向对象范式的一些特性。它们实现了封装、数据隐藏和继承,但它们也有一些局限性。例如,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关键字定义的。

  • 字段是类每个实例中存在的数据项。

  • 方法在类的定义中声明。

  • 根类中有一个预定义的构造函数,名为Create。每个抽象类和每个具体类都是根类的后代,因此所有类至少有一个构造函数。

  • 根类中有一个预定义的析构函数,名为Destroy。每个抽象类和每个具体类都是根类的后代,因此所有类至少有一个析构函数。

让我们定义一个矩形类,它有两个整数类型的数据成员 - 长度和宽度,以及一些用于操作这些数据成员的成员函数和一个用于绘制矩形的过程。

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} // directive to be used for defining classes
{$m+}		   // directive to be used for using constructor

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 类成员有五种可见性类型:

序号 可见性和可访问性
1

公共 (Public)

这些成员始终可访问。

2

私有 (Private)

这些成员只能在包含类定义的模块或单元中访问。它们可以从类方法内部或外部访问。

3

严格私有 (Strict Private)

这些成员只能从类本身的方法访问。同一单元中的其他类或子类无法访问它们。

4

受保护 (Protected)

这与私有相同,只是这些成员可被子类型访问,即使它们是在其他模块中实现的。

5

已发布 (Published)

这与公共相同,但是如果编译器处于{$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 类有两个构造函数,因此overload运算符用于函数重载。

  • Books.Display 过程已声明为virtual,以便来自 Novels 类的相同方法可以override它。

  • Novels.Create 构造函数使用inherited关键字调用基类构造函数。

接口

定义接口是为了为实现者提供一个通用的函数名称。不同的实现者可以根据自己的需求实现这些接口。您可以说,接口是框架,由开发人员实现。以下是一个接口示例:

type  
   Mail = Interface  
      Procedure SendMail;  
      Procedure GetMail;  
   end;  
   
   Report = Class(TInterfacedObject,  Mail)  
      Procedure SendMail;  
      Procedure GetMail;  
   end;  

请注意,当一个类实现一个接口时,它应该实现接口的所有方法。如果未实现接口的方法,则编译器将给出错误。

抽象类

抽象类是不能实例化,只能继承的类。抽象类是通过在类定义中包含单词符号 abstract 来指定的,如下所示:

type
   Shape = ABSTRACT CLASS (Root)
      Procedure draw; ABSTRACT;
      ...
   end;

从抽象类继承时,必须由子类定义父类声明中标记为抽象的所有方法;此外,这些方法必须使用相同的可见性定义。

静态关键字

将类成员或方法声明为静态,使它们无需实例化类即可访问。声明为静态的成员不能使用实例化的类对象访问(尽管可以访问静态方法)。以下示例说明了这个概念:

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}才能使用静态成员。

广告
© . All rights reserved.